PageSourceSearch

https://www.pullmanparistoureiffel.fr/js/addons/video.js

js pullmanparistoureiffel.fr collected 2026-10-02 04:56:08 UTC 1,942,237 bytes, 60,412 lines download raw bytes

1/**
2 * @license
3 * Video.js 7.10.2 <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(require('global/window'), require('global/document')) :
15  typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) :
16  (global = global || self, global.videojs = factory(global.window, global.document));
17}(this, (function (window$1, document) { 'use strict';
18
19  window$1 = window$1 && Object.prototype.hasOwnProperty.call(window$1, 'default') ? window$1['default'] : window$1;
20  document = document && Object.prototype.hasOwnProperty.call(document, 'default') ? document['default'] : document;
21
22  var version = "7.10.2";
23
24  /**
25   * @file create-logger.js
26   * @module create-logger
27   */
28
29  var history = [];
30  /**
31   * Log messages to the console and history based on the type of message
32   *
33   * @private
34   * @param  {string} type
35   *         The name of the console method to use.
36   *
37   * @param  {Array} args
38   *         The arguments to be passed to the matching console method.
39   */
40
41  var LogByTypeFactory = function LogByTypeFactory(name, log) {
42    return function (type, level, args) {
43      var lvl = log.levels[level];
44      var lvlRegExp = new RegExp("^(" + lvl + ")$");
45
46      if (type !== 'log') {
47        // Add the type to the front of the message when it's not "log".
48        args.unshift(type.toUpperCase() + ':');
49      } // Add console prefix after adding to history.
50
51
52      args.unshift(name + ':'); // Add a clone of the args at this point to history.
53
54      if (history) {
55        history.push([].concat(args)); // only store 1000 history entries
56
57        var splice = history.length - 1000;
58        history.splice(0, splice > 0 ? splice : 0);
59      } // If there's no console then don't try to output messages, but they will
60      // still be stored in history.
61
62
63      if (!window$1.console) {
64        return;
65      } // Was setting these once outside of this function, but containing them
66      // in the function makes it easier to test cases where console doesn't exist
67      // when the module is executed.
68
69
70      var fn = window$1.console[type];
71
72      if (!fn && type === 'debug') {
73        // Certain browsers don't have support for console.debug. For those, we
74        // should default to the closest comparable log.
75        fn = window$1.console.info || window$1.console.log;
76      } // Bail out if there's no console or if this type is not allowed by the
77      // current logging level.
78
79
80      if (!fn || !lvl || !lvlRegExp.test(type)) {
81        return;
82      }
83
84      fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
85    };
86  };
87
88  function createLogger(name) {
89    // This is the private tracking variable for logging level.
90    var level = 'info'; // the curried logByType bound to the specific log and history
91
92    var logByType;
93    /**
94     * Logs plain debug messages. Similar to `console.log`.
95     *
96     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
97     * of our JSDoc template, we cannot properly document this as both a function
98     * and a namespace, so its function signature is documented here.
99     *
100     * #### Arguments
101     * ##### *args
102     * Mixed[]
103     *
104     * Any combination of values that could be passed to `console.log()`.
105     *
106     * #### Return Value
107     *
108     * `undefined`
109     *
110     * @namespace
111     * @param    {Mixed[]} args
112     *           One or more messages or objects that should be logged.
113     */
114
115    var log = function log() {
116      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
117        args[_key] = arguments[_key];
118      }
119
vendor: 4,289 bytes, lines 120-271
120      logByType('log', level, args);
121    }; // This is the logByType helper that the logging methods below use
122
123
124    logByType = LogByTypeFactory(name, log);
125    /**
126     * Create a new sublogger which chains the old name to the new name.
127     *
128     * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
129     * ```js
130     *  mylogger('foo');
131     *  // > VIDEOJS: player: foo
132     * ```
133     *
134     * @param {string} name
135     *        The name to add call the new logger
136     * @return {Object}
137     */
138
139    log.createLogger = function (subname) {
140      return createLogger(name + ': ' + subname);
141    };
142    /**
143     * Enumeration of available logging levels, where the keys are the level names
144     * and the values are `|`-separated strings containing logging methods allowed
145     * in that logging level. These strings are used to create a regular expression
146     * matching the function name being called.
147     *
148     * Levels provided by Video.js are:
149     *
150     * - `off`: Matches no calls. Any value that can be cast to `false` will have
151     *   this effect. The most restrictive.
152     * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
153     *   `log.warn`, and `log.error`).
154     * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
155     * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
156     * - `warn`: Matches `log.warn` and `log.error` calls.
157     * - `error`: Matches only `log.error` calls.
158     *
159     * @type {Object}
160     */
161
162
163    log.levels = {
164      all: 'debug|log|warn|error',
165      off: '',
166      debug: 'debug|log|warn|error',
167      info: 'log|warn|error',
168      warn: 'warn|error',
169      error: 'error',
170      DEFAULT: level
171    };
172    /**
173     * Get or set the current logging level.
174     *
175     * If a string matching a key from {@link module:log.levels} is provided, acts
176     * as a setter.
177     *
178     * @param  {string} [lvl]
179     *         Pass a valid level to set a new logging level.
180     *
181     * @return {string}
182     *         The current logging level.
183     */
184
185    log.level = function (lvl) {
186      if (typeof lvl === 'string') {
187        if (!log.levels.hasOwnProperty(lvl)) {
188          throw new Error("\"" + lvl + "\" in not a valid log level");
189        }
190
191        level = lvl;
192      }
193
194      return level;
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
206
207    log.history = function () {
208      return history ? [].concat(history) : [];
209    };
210    /**
211     * Allows you to filter the history by the given logger name
212     *
213     * @param {string} fname
214     *        The name to filter by
215     *
216     * @return {Array}
217     *         The filtered list to return
218     */
219
220
221    log.history.filter = function (fname) {
222      return (history || []).filter(function (historyItem) {
223        // if the first item in each historyItem includes `fname`, then it's a match
224        return new RegExp(".*" + fname + ".*").test(historyItem[0]);
225      });
226    };
227    /**
228     * Clears the internal history tracking, but does not prevent further history
229     * tracking.
230     */
231
232
233    log.history.clear = function () {
234      if (history) {
235        history.length = 0;
236      }
237    };
238    /**
239     * Disable history tracking if it is currently enabled.
240     */
241
242
243    log.history.disable = function () {
244      if (history !== null) {
245        history.length = 0;
246        history = null;
247      }
248    };
249    /**
250     * Enable history tracking if it is currently disabled.
251     */
252
253
254    log.history.enable = function () {
255      if (history === null) {
256        history = [];
257      }
258    };
259    /**
260     * Logs error messages. Similar to `console.error`.
261     *
262     * @param {Mixed[]} args
263     *        One or more messages or objects that should be logged as an error
264     */
265
266
267    log.error = function () {
268      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
269        args[_key2] = arguments[_key2];
270      }
271
272      return logByType('error', level, args);
273    };
274    /**
275     * Logs warning messages. Similar to `console.warn`.
276     *
277     * @param {Mixed[]} args
278     *        One or more messages or objects that should be logged as a warning.
279     */
280
281
282    log.warn = function () {
283      for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
284        args[_key3] = arguments[_key3];
285      }
286
287      return logByType('warn', level, args);
288    };
289    /**
290     * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
291     * log if `console.debug` is not available
292     *
293     * @param {Mixed[]} args
294     *        One or more messages or objects that should be logged as debug.
295     */
296
297
298    log.debug = function () {
299      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
300        args[_key4] = arguments[_key4];
301      }
302
303      return logByType('debug', level, args);
304    };
305
306    return log;
307  }
308
309  /**
310   * @file log.js
311   * @module log
312   */
313  var log = createLogger('VIDEOJS');
314  var createLogger$1 = log.createLogger;
315
316  function unwrapExports (x) {
317  	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
318  }
319
320  function createCommonjsModule(fn, module) {
321  	return module = { exports: {} }, fn(module, module.exports), module.exports;
322  }
323
324  var _extends_1 = createCommonjsModule(function (module) {
325    function _extends() {
326      module.exports = _extends = Object.assign || function (target) {
327        for (var i = 1; i < arguments.length; i++) {
328          var source = arguments[i];
329
330          for (var key in source) {
331            if (Object.prototype.hasOwnProperty.call(source, key)) {
332              target[key] = source[key];
333            }
334          }
335        }
336
337        return target;
338      };
339
340      return _extends.apply(this, arguments);
341    }
342
343    module.exports = _extends;
344  });
345
346  /**
347   * @file obj.js
348   * @module obj
349   */
350
351  /**
352   * @callback obj:EachCallback
353   *
354   * @param {Mixed} value
355   *        The current key for the object that is being iterated over.
356   *
357   * @param {string} key
358   *        The current key-value for object that is being iterated over
359   */
360
361  /**
362   * @callback obj:ReduceCallback
363   *
364   * @param {Mixed} accum
365   *        The value that is accumulating over the reduce loop.
366   *
367   * @param {Mixed} value
368   *        The current key for the object that is being iterated over.
369   *
370   * @param {string} key
371   *        The current key-value for object that is being iterated over
372   *
373   * @return {Mixed}
374   *         The new accumulated value.
375   */
376  var toString = Object.prototype.toString;
377  /**
378   * Get the keys of an Object
379   *
380   * @param {Object}
381   *        The Object to get the keys from
382   *
383   * @return {string[]}
384   *         An array of the keys from the object. Returns an empty array if the
385   *         object passed in was invalid or had no keys.
386   *
387   * @private
388   */
389
390  var keys = function keys(object) {
391    return isObject(object) ? Object.keys(object) : [];
392  };
393  /**
394   * Array-like iteration for objects.
395   *
396   * @param {Object} object
397   *        The object to iterate over
398   *
399   * @param {obj:EachCallback} fn
400   *        The callback function which is called for each key in the object.
401   */
402
403
404  function each(object, fn) {
405    keys(object).forEach(function (key) {
406      return fn(object[key], key);
407    });
408  }
409  /**
410   * Array-like reduce for objects.
411   *
412   * @param {Object} object
413   *        The Object that you want to reduce.
414   *
415   * @param {Function} fn
416   *         A callback function which is called for each key in the object. It
417   *         receives the accumulated value and the per-iteration value and key
418   *         as arguments.
419   *
420   * @param {Mixed} [initial = 0]
421   *        Starting value
422   *
423   * @return {Mixed}
424   *         The final accumulated value.
425   */
426
427  function reduce(object, fn, initial) {
428    if (initial === void 0) {
429      initial = 0;
430    }
431
432    return keys(object).reduce(function (accum, key) {
433      return fn(accum, object[key], key);
434    }, initial);
435  }
436  /**
437   * Object.assign-style object shallow merge/extend.
438   *
439   * @param  {Object} target
440   * @param  {Object} ...sources
441   * @return {Object}
442   */
443
444  function assign(target) {
445    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
446      sources[_key - 1] = arguments[_key];
447    }
448
449    if (Object.assign) {
450      return _extends_1.apply(void 0, [target].concat(sources));
451    }
452
vendor: 15,680 bytes, lines 453-1012
453    sources.forEach(function (source) {
454      if (!source) {
455        return;
456      }
457
458      each(source, function (value, key) {
459        target[key] = value;
460      });
461    });
462    return target;
463  }
464  /**
465   * Returns whether a value is an object of any kind - including DOM nodes,
466   * arrays, regular expressions, etc. Not functions, though.
467   *
468   * This avoids the gotcha where using `typeof` on a `null` value
469   * results in `'object'`.
470   *
471   * @param  {Object} value
472   * @return {boolean}
473   */
474
475  function isObject(value) {
476    return !!value && typeof value === 'object';
477  }
478  /**
479   * Returns whether an object appears to be a "plain" object - that is, a
480   * direct instance of `Object`.
481   *
482   * @param  {Object} value
483   * @return {boolean}
484   */
485
486  function isPlain(value) {
487    return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
488  }
489
490  /**
491   * @file computed-style.js
492   * @module computed-style
493   */
494  /**
495   * A safe getComputedStyle.
496   *
497   * This is needed because in Firefox, if the player is loaded in an iframe with
498   * `display:none`, then `getComputedStyle` returns `null`, so, we do a
499   * null-check to make sure that the player doesn't break in these cases.
500   *
501   * @function
502   * @param    {Element} el
503   *           The element you want the computed style of
504   *
505   * @param    {string} prop
506   *           The property name you want
507   *
508   * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
509   */
510
511  function computedStyle(el, prop) {
512    if (!el || !prop) {
513      return '';
514    }
515
516    if (typeof window$1.getComputedStyle === 'function') {
517      var computedStyleValue = window$1.getComputedStyle(el);
518      return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
519    }
520
521    return '';
522  }
523
524  /**
525   * @file dom.js
526   * @module dom
527   */
528  /**
529   * Detect if a value is a string with any non-whitespace characters.
530   *
531   * @private
532   * @param  {string} str
533   *         The string to check
534   *
535   * @return {boolean}
536   *         Will be `true` if the string is non-blank, `false` otherwise.
537   *
538   */
539
540  function isNonBlankString(str) {
541    // we use str.trim as it will trim any whitespace characters
542    // from the front or back of non-whitespace characters. aka
543    // Any string that contains non-whitespace characters will
544    // still contain them after `trim` but whitespace only strings
545    // will have a length of 0, failing this check.
546    return typeof str === 'string' && Boolean(str.trim());
547  }
548  /**
549   * Throws an error if the passed string has whitespace. This is used by
550   * class methods to be relatively consistent with the classList API.
551   *
552   * @private
553   * @param  {string} str
554   *         The string to check for whitespace.
555   *
556   * @throws {Error}
557   *         Throws an error if there is whitespace in the string.
558   */
559
560
561  function throwIfWhitespace(str) {
562    // str.indexOf instead of regex because str.indexOf is faster performance wise.
563    if (str.indexOf(' ') >= 0) {
564      throw new Error('class has illegal whitespace characters');
565    }
566  }
567  /**
568   * Produce a regular expression for matching a className within an elements className.
569   *
570   * @private
571   * @param  {string} className
572   *         The className to generate the RegExp for.
573   *
574   * @return {RegExp}
575   *         The RegExp that will check for a specific `className` in an elements
576   *         className.
577   */
578
579
580  function classRegExp(className) {
581    return new RegExp('(^|\\s)' + className + '($|\\s)');
582  }
583  /**
584   * Whether the current DOM interface appears to be real (i.e. not simulated).
585   *
586   * @return {boolean}
587   *         Will be `true` if the DOM appears to be real, `false` otherwise.
588   */
589
590
591  function isReal() {
592    // Both document and window will never be undefined thanks to `global`.
593    return document === window$1.document;
594  }
595  /**
596   * Determines, via duck typing, whether or not a value is a DOM element.
597   *
598   * @param  {Mixed} value
599   *         The value to check.
600   *
601   * @return {boolean}
602   *         Will be `true` if the value is a DOM element, `false` otherwise.
603   */
604
605  function isEl(value) {
606    return isObject(value) && value.nodeType === 1;
607  }
608  /**
609   * Determines if the current DOM is embedded in an iframe.
610   *
611   * @return {boolean}
612   *         Will be `true` if the DOM is embedded in an iframe, `false`
613   *         otherwise.
614   */
615
616  function isInFrame() {
617    // We need a try/catch here because Safari will throw errors when attempting
618    // to get either `parent` or `self`
619    try {
620      return window$1.parent !== window$1.self;
621    } catch (x) {
622      return true;
623    }
624  }
625  /**
626   * Creates functions to query the DOM using a given method.
627   *
628   * @private
629   * @param   {string} method
630   *          The method to create the query with.
631   *
632   * @return  {Function}
633   *          The query method
634   */
635
636  function createQuerier(method) {
637    return function (selector, context) {
638      if (!isNonBlankString(selector)) {
639        return document[method](null);
640      }
641
642      if (isNonBlankString(context)) {
643        context = document.querySelector(context);
644      }
645
646      var ctx = isEl(context) ? context : document;
647      return ctx[method] && ctx[method](selector);
648    };
649  }
650  /**
651   * Creates an element and applies properties, attributes, and inserts content.
652   *
653   * @param  {string} [tagName='div']
654   *         Name of tag to be created.
655   *
656   * @param  {Object} [properties={}]
657   *         Element properties to be applied.
658   *
659   * @param  {Object} [attributes={}]
660   *         Element attributes to be applied.
661   *
662   * @param {module:dom~ContentDescriptor} content
663   *        A content descriptor object.
664   *
665   * @return {Element}
666   *         The element that was created.
667   */
668
669
670  function createEl(tagName, properties, attributes, content) {
671    if (tagName === void 0) {
672      tagName = 'div';
673    }
674
675    if (properties === void 0) {
676      properties = {};
677    }
678
679    if (attributes === void 0) {
680      attributes = {};
681    }
682
683    var el = document.createElement(tagName);
684    Object.getOwnPropertyNames(properties).forEach(function (propName) {
685      var val = properties[propName]; // See #2176
686      // We originally were accepting both properties and attributes in the
687      // same object, but that doesn't work so well.
688
689      if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
690        log.warn('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 " + propName + " to " + val + "."));
691        el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
692        // method for it.
693      } else if (propName === 'textContent') {
694        textContent(el, val);
695      } else if (el[propName] !== val) {
696        el[propName] = val;
697      }
698    });
699    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
700      el.setAttribute(attrName, attributes[attrName]);
701    });
702
703    if (content) {
704      appendContent(el, content);
705    }
706
707    return el;
708  }
709  /**
710   * Injects text into an element, replacing any existing contents entirely.
711   *
712   * @param  {Element} el
713   *         The element to add text content into
714   *
715   * @param  {string} text
716   *         The text content to add.
717   *
718   * @return {Element}
719   *         The element with added text content.
720   */
721
722  function textContent(el, text) {
723    if (typeof el.textContent === 'undefined') {
724      el.innerText = text;
725    } else {
726      el.textContent = text;
727    }
728
729    return el;
730  }
731  /**
732   * Insert an element as the first child node of another
733   *
734   * @param {Element} child
735   *        Element to insert
736   *
737   * @param {Element} parent
738   *        Element to insert child into
739   */
740
741  function prependTo(child, parent) {
742    if (parent.firstChild) {
743      parent.insertBefore(child, parent.firstChild);
744    } else {
745      parent.appendChild(child);
746    }
747  }
748  /**
749   * Check if an element has a class name.
750   *
751   * @param  {Element} element
752   *         Element to check
753   *
754   * @param  {string} classToCheck
755   *         Class name to check for
756   *
757   * @return {boolean}
758   *         Will be `true` if the element has a class, `false` otherwise.
759   *
760   * @throws {Error}
761   *         Throws an error if `classToCheck` has white space.
762   */
763
764  function hasClass(element, classToCheck) {
765    throwIfWhitespace(classToCheck);
766
767    if (element.classList) {
768      return element.classList.contains(classToCheck);
769    }
770
771    return classRegExp(classToCheck).test(element.className);
772  }
773  /**
774   * Add a class name to an element.
775   *
776   * @param  {Element} element
777   *         Element to add class name to.
778   *
779   * @param  {string} classToAdd
780   *         Class name to add.
781   *
782   * @return {Element}
783   *         The DOM element with the added class name.
784   */
785
786  function addClass(element, classToAdd) {
787    if (element.classList) {
788      element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
789      // in the case of classList not being supported.
790    } else if (!hasClass(element, classToAdd)) {
791      element.className = (element.className + ' ' + classToAdd).trim();
792    }
793
794    return element;
795  }
796  /**
797   * Remove a class name from an element.
798   *
799   * @param  {Element} element
800   *         Element to remove a class name from.
801   *
802   * @param  {string} classToRemove
803   *         Class name to remove
804   *
805   * @return {Element}
806   *         The DOM element with class name removed.
807   */
808
809  function removeClass(element, classToRemove) {
810    if (element.classList) {
811      element.classList.remove(classToRemove);
812    } else {
813      throwIfWhitespace(classToRemove);
814      element.className = element.className.split(/\s+/).filter(function (c) {
815        return c !== classToRemove;
816      }).join(' ');
817    }
818
819    return element;
820  }
821  /**
822   * The callback definition for toggleClass.
823   *
824   * @callback module:dom~PredicateCallback
825   * @param    {Element} element
826   *           The DOM element of the Component.
827   *
828   * @param    {string} classToToggle
829   *           The `className` that wants to be toggled
830   *
831   * @return   {boolean|undefined}
832   *           If `true` is returned, the `classToToggle` will be added to the
833   *           `element`. If `false`, the `classToToggle` will be removed from
834   *           the `element`. If `undefined`, the callback will be ignored.
835   */
836
837  /**
838   * Adds or removes a class name to/from an element depending on an optional
839   * condition or the presence/absence of the class name.
840   *
841   * @param  {Element} element
842   *         The element to toggle a class name on.
843   *
844   * @param  {string} classToToggle
845   *         The class that should be toggled.
846   *
847   * @param  {boolean|module:dom~PredicateCallback} [predicate]
848   *         See the return value for {@link module:dom~PredicateCallback}
849   *
850   * @return {Element}
851   *         The element with a class that has been toggled.
852   */
853
854  function toggleClass(element, classToToggle, predicate) {
855    // This CANNOT use `classList` internally because IE11 does not support the
856    // second parameter to the `classList.toggle()` method! Which is fine because
857    // `classList` will be used by the add/remove functions.
858    var has = hasClass(element, classToToggle);
859
860    if (typeof predicate === 'function') {
861      predicate = predicate(element, classToToggle);
862    }
863
864    if (typeof predicate !== 'boolean') {
865      predicate = !has;
866    } // If the necessary class operation matches the current state of the
867    // element, no action is required.
868
869
870    if (predicate === has) {
871      return;
872    }
873
874    if (predicate) {
875      addClass(element, classToToggle);
876    } else {
877      removeClass(element, classToToggle);
878    }
879
880    return element;
881  }
882  /**
883   * Apply attributes to an HTML element.
884   *
885   * @param {Element} el
886   *        Element to add attributes to.
887   *
888   * @param {Object} [attributes]
889   *        Attributes to be applied.
890   */
891
892  function setAttributes(el, attributes) {
893    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
894      var attrValue = attributes[attrName];
895
896      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
897        el.removeAttribute(attrName);
898      } else {
899        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
900      }
901    });
902  }
903  /**
904   * Get an element's attribute values, as defined on the HTML tag.
905   *
906   * Attributes are not the same as properties. They're defined on the tag
907   * or with setAttribute.
908   *
909   * @param  {Element} tag
910   *         Element from which to get tag attributes.
911   *
912   * @return {Object}
913   *         All attributes of the element. Boolean attributes will be `true` or
914   *         `false`, others will be strings.
915   */
916
917  function getAttributes(tag) {
918    var obj = {}; // known boolean attributes
919    // we can check for matching boolean properties, but not all browsers
920    // and not all tags know about these attributes, so, we still want to check them manually
921
922    var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
923
924    if (tag && tag.attributes && tag.attributes.length > 0) {
925      var attrs = tag.attributes;
926
927      for (var i = attrs.length - 1; i >= 0; i--) {
928        var attrName = attrs[i].name;
929        var attrVal = attrs[i].value; // check for known booleans
930        // the matching element property will return a value for typeof
931
932        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
933          // the value of an included boolean attribute is typically an empty
934          // string ('') which would equal false if we just check for a false value.
935          // we also don't want support bad code like autoplay='false'
936          attrVal = attrVal !== null ? true : false;
937        }
938
939        obj[attrName] = attrVal;
940      }
941    }
942
943    return obj;
944  }
945  /**
946   * Get the value of an element's attribute.
947   *
948   * @param {Element} el
949   *        A DOM element.
950   *
951   * @param {string} attribute
952   *        Attribute to get the value of.
953   *
954   * @return {string}
955   *         The value of the attribute.
956   */
957
958  function getAttribute(el, attribute) {
959    return el.getAttribute(attribute);
960  }
961  /**
962   * Set the value of an element's attribute.
963   *
964   * @param {Element} el
965   *        A DOM element.
966   *
967   * @param {string} attribute
968   *        Attribute to set.
969   *
970   * @param {string} value
971   *        Value to set the attribute to.
972   */
973
974  function setAttribute(el, attribute, value) {
975    el.setAttribute(attribute, value);
976  }
977  /**
978   * Remove an element's attribute.
979   *
980   * @param {Element} el
981   *        A DOM element.
982   *
983   * @param {string} attribute
984   *        Attribute to remove.
985   */
986
987  function removeAttribute(el, attribute) {
988    el.removeAttribute(attribute);
989  }
990  /**
991   * Attempt to block the ability to select text.
992   */
993
994  function blockTextSelection() {
995    document.body.focus();
996
997    document.onselectstart = function () {
998      return false;
999    };
1000  }
1001  /**
1002   * Turn off text selection blocking.
1003   */
1004
1005  function unblockTextSelection() {
1006    document.onselectstart = function () {
1007      return true;
1008    };
1009  }
1010  /**
1011   * Identical to the native `getBoundingClientRect` function, but ensures that
1012   * the method is supported at all (it is in all browsers we claim to supp
1012ort)
1013   * and that the element is in the DOM before continuing.
1014   *
1015   * This wrapper function also shims properties which are not provided by some
1016   * older browsers (namely, IE8).
1017   *
1018   * Additionally, some browsers do not support adding properties to a
1019   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1020   * properties (except `x` and `y` which are not widely supported). This helps
1021   * avoid implementations where keys are non-enumerable.
1022   *
1023   * @param  {Element} el
1024   *         Element whose `ClientRect` we want to calculate.
1025   *
1026   * @return {Object|undefined}
1027   *         Always returns a plain object - or `undefined` if it cannot.
1028   */
1029
1030  function getBoundingClientRect(el) {
1031    if (el && el.getBoundingClientRect && el.parentNode) {
1032      var rect = el.getBoundingClientRect();
1033      var result = {};
1034      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1035        if (rect[k] !== undefined) {
1036          result[k] = rect[k];
1037        }
1038      });
1039
1040      if (!result.height) {
1041        result.height = parseFloat(computedStyle(el, 'height'));
1042      }
1043
1044      if (!result.width) {
1045        result.width = parseFloat(computedStyle(el, 'width'));
1046      }
1047
1048      return result;
1049    }
1050  }
1051  /**
1052   * Represents the position of a DOM element on the page.
1053   *
1054   * @typedef  {Object} module:dom~Position
1055   *
1056   * @property {number} left
1057   *           Pixels to the left.
1058   *
1059   * @property {number} top
1060   *           Pixels from the top.
1061   */
1062
1063  /**
1064   * Get the position of an element in the DOM.
1065   *
1066   * Uses `getBoundingClientRect` technique from John Resig.
1067   *
1068   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1069   *
1070   * @param  {Element} el
1071   *         Element from which to get offset.
1072   *
1073   * @return {module:dom~Position}
1074   *         The position of the element that was passed in.
1075   */
1076
1077  function findPosition(el) {
1078    if (!el || el && !el.offsetParent) {
1079      return {
1080        left: 0,
1081        top: 0,
1082        width: 0,
1083        height: 0
1084      };
1085    }
1086
1087    var width = el.offsetWidth;
1088    var height = el.offsetHeight;
1089    var left = 0;
1090    var top = 0;
1091
1092    do {
1093      left += el.offsetLeft;
1094      top += el.offsetTop;
1095      el = el.offsetParent;
1096    } while (el);
1097
1098    return {
1099      left: left,
1100      top: top,
1101      width: width,
1102      height: height
1103    };
1104  }
1105  /**
1106   * Represents x and y coordinates for a DOM element or mouse pointer.
1107   *
1108   * @typedef  {Object} module:dom~Coordinates
1109   *
1110   * @property {number} x
1111   *           x coordinate in pixels
1112   *
1113   * @property {number} y
1114   *           y coordinate in pixels
1115   */
1116
1117  /**
1118   * Get the pointer position within an element.
1119   *
1120   * The base on the coordinates are the bottom left of the element.
1121   *
1122   * @param  {Element} el
1123   *         Element on which to get the pointer position on.
1124   *
1125   * @param  {EventTarget~Event} event
1126   *         Event object.
1127   *
1128   * @return {module:dom~Coordinates}
1129   *         A coordinates object corresponding to the mouse position.
1130   *
1131   */
1132
1133  function getPointerPosition(el, event) {
1134    var position = {};
1135    var boxTarget = findPosition(event.target);
1136    var box = findPosition(el);
1137    var boxW = box.width;
1138    var boxH = box.height;
1139    var offsetY = event.offsetY - (box.top - boxTarget.top);
1140    var offsetX = event.offsetX - (box.left - boxTarget.left);
1141
1142    if (event.changedTouches) {
1143      offsetX = event.changedTouches[0].pageX - box.left;
1144      offsetY = event.changedTouches[0].pageY + box.top;
1145    }
1146
1147    position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
1148    position.x = Math.max(0, Math.min(1, offsetX / boxW));
1149    return position;
1150  }
1151  /**
1152   * Determines, via duck typing, whether or not a value is a text node.
1153   *
1154   * @param  {Mixed} value
1155   *         Check if this value is a text node.
1156   *
1157   * @return {boolean}
1158   *         Will be `true` if the value is a text node, `false` otherwise.
1159   */
1160
1161  function isTextNode(value) {
1162    return isObject(value) && value.nodeType === 3;
1163  }
1164  /**
1165   * Empties the contents of an element.
1166   *
1167   * @param  {Element} el
1168   *         The element to empty children from
1169   *
1170   * @return {Element}
1171   *         The element with no children
1172   */
1173
1174  function emptyEl(el) {
1175    while (el.firstChild) {
1176      el.removeChild(el.firstChild);
1177    }
1178
1179    return el;
1180  }
1181  /**
1182   * This is a mixed value that describes content to be injected into the DOM
1183   * via some method. It can be of the following types:
1184   *
1185   * Type       | Description
1186   * -----------|-------------
1187   * `string`   | The value will be normalized into a text node.
1188   * `Element`  | The value will be accepted as-is.
1189   * `TextNode` | The value will be accepted as-is.
1190   * `Array`    | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
1191   * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
1192   *
1193   * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
1194   */
1195
1196  /**
1197   * Normalizes content for eventual insertion into the DOM.
1198   *
1199   * This allows a wide range of content definition methods, but helps protect
1200   * from falling into the trap of simply writing to `innerHTML`, which could
1201   * be an XSS concern.
1202   *
1203   * The content for an element can be passed in multiple types and
1204   * combinations, whose behavior is as follows:
1205   *
1206   * @param {module:dom~ContentDescriptor} content
1207   *        A content descriptor value.
1208   *
1209   * @return {Array}
1210   *         All of the content that was passed in, normalized to an array of
1211   *         elements or text nodes.
1212   */
1213
1214  function normalizeContent(content) {
1215    // First, invoke content if it is a function. If it produces an array,
1216    // that needs to happen before normalization.
1217    if (typeof content === 'function') {
1218      content = content();
1219    } // Next up, normalize to an array, so one or many items can be normalized,
1220    // filtered, and returned.
1221
1222
1223    return (Array.isArray(content) ? content : [content]).map(function (value) {
1224      // First, invoke value if it is a function to produce a new value,
1225      // which will be subsequently normalized to a Node of some kind.
1226      if (typeof value === 'function') {
1227        value = value();
1228      }
1229
1230      if (isEl(value) || isTextNode(value)) {
1231        return value;
1232      }
1233
1234      if (typeof value === 'string' && /\S/.test(value)) {
1235        return document.createTextNode(value);
1236      }
1237    }).filter(function (value) {
1238      return value;
1239    });
1240  }
1241  /**
1242   * Normalizes and appends content to an element.
1243   *
1244   * @param  {Element} el
1245   *         Element to append normalized content to.
1246   *
1247   * @param {module:dom~ContentDescriptor} content
1248   *        A content descriptor value.
1249   *
1250   * @return {Element}
1251   *         The element with appended normalized content.
1252   */
1253
1254  function appendContent(el, content) {
1255    normalizeContent(content).forEach(function (node) {
1256      return el.appendChild(node);
1257    });
1258    return el;
1259  }
1260  /**
1261   * Normalizes and inserts content into an element; this is identical to
1262   * `appendContent()`, except it empties the element first.
1263   *
1264   * @param {Element} el
1265   *        Element to insert normalized content into.
1266   *
1267   * @param {module:dom~ContentDescriptor} content
1268   *        A content descriptor value.
1269   *
1270   * @return {Element}
1271   *         The element with inserted normalized content.
1272   */
1273
1274  function insertContent(el, content) {
1275    return appendContent(emptyEl(el), content);
1276  }
1277  /**
1278   * Check if an event was a single left click.
1279   *
1280   * @param  {EventTarget~Event} event
1281   *         Event object.
1282   *
1283   * @return {boolean}
1284   *         Will be `true` if a single left click, `false` otherwise.
1285   */
1286
1287  function isSingleLeftClick(event) {
1288    // Note: if you create something draggable, be sure to
1289    // call it on both `mousedown` and `mousemove` event,
1290    // otherwise `mousedown` should be enough for a button
1291    if (event.button === undefined && event.buttons === undefined) {
1292      // Why do we need `buttons` ?
1293      // Because, middle mouse sometimes have this:
1294      // e.button === 0 and e.buttons === 4
1295      // Furthermore, we want to prevent combination click, something like
1296      // HOLD middlemouse then left click, that would be
1297      // e.button === 0, e.buttons === 5
1298      // just `button` is not gonna work
1299      // Alright, then what this block does ?
1300      // this is for chrome `simulate mobile devices`
vendor: 36,459 bytes, lines 1301-2568
1301      // I want to support this as well
1302      return true;
1303    }
1304
1305    if (event.button === 0 && event.buttons === undefined) {
1306      // Touch screen, sometimes on some specific device, `buttons`
1307      // doesn't have anything (safari on ios, blackberry...)
1308      return true;
1309    } // `mouseup` event on a single left click has
1310    // `button` and `buttons` equal to 0
1311
1312
1313    if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
1314      return true;
1315    }
1316
1317    if (event.button !== 0 || event.buttons !== 1) {
1318      // This is the reason we have those if else block above
1319      // if any special case we can catch and let it slide
1320      // we do it above, when get to here, this definitely
1321      // is-not-left-click
1322      return false;
1323    }
1324
1325    return true;
1326  }
1327  /**
1328   * Finds a single DOM element matching `selector` within the optional
1329   * `context` of another DOM element (defaulting to `document`).
1330   *
1331   * @param  {string} selector
1332   *         A valid CSS selector, which will be passed to `querySelector`.
1333   *
1334   * @param  {Element|String} [context=document]
1335   *         A DOM element within which to query. Can also be a selector
1336   *         string in which case the first matching element will be used
1337   *         as context. If missing (or no element matches selector), falls
1338   *         back to `document`.
1339   *
1340   * @return {Element|null}
1341   *         The element that was found or null.
1342   */
1343
1344  var $ = createQuerier('querySelector');
1345  /**
1346   * Finds a all DOM elements matching `selector` within the optional
1347   * `context` of another DOM element (defaulting to `document`).
1348   *
1349   * @param  {string} selector
1350   *         A valid CSS selector, which will be passed to `querySelectorAll`.
1351   *
1352   * @param  {Element|String} [context=document]
1353   *         A DOM element within which to query. Can also be a selector
1354   *         string in which case the first matching element will be used
1355   *         as context. If missing (or no element matches selector), falls
1356   *         back to `document`.
1357   *
1358   * @return {NodeList}
1359   *         A element list of elements that were found. Will be empty if none
1360   *         were found.
1361   *
1362   */
1363
1364  var $$ = createQuerier('querySelectorAll');
1365
1366  var Dom = /*#__PURE__*/Object.freeze({
1367    __proto__: null,
1368    isReal: isReal,
1369    isEl: isEl,
1370    isInFrame: isInFrame,
1371    createEl: createEl,
1372    textContent: textContent,
1373    prependTo: prependTo,
1374    hasClass: hasClass,
1375    addClass: addClass,
1376    removeClass: removeClass,
1377    toggleClass: toggleClass,
1378    setAttributes: setAttributes,
1379    getAttributes: getAttributes,
1380    getAttribute: getAttribute,
1381    setAttribute: setAttribute,
1382    removeAttribute: removeAttribute,
1383    blockTextSelection: blockTextSelection,
1384    unblockTextSelection: unblockTextSelection,
1385    getBoundingClientRect: getBoundingClientRect,
1386    findPosition: findPosition,
1387    getPointerPosition: getPointerPosition,
1388    isTextNode: isTextNode,
1389    emptyEl: emptyEl,
1390    normalizeContent: normalizeContent,
1391    appendContent: appendContent,
1392    insertContent: insertContent,
1393    isSingleLeftClick: isSingleLeftClick,
1394    $: $,
1395    $$: $$
1396  });
1397
1398  /**
1399   * @file setup.js - Functions for setting up a player without
1400   * user interaction based on the data-setup `attribute` of the video tag.
1401   *
1402   * @module setup
1403   */
1404  var _windowLoaded = false;
1405  var videojs;
1406  /**
1407   * Set up any tags that have a data-setup `attribute` when the player is started.
1408   */
1409
1410  var autoSetup = function autoSetup() {
1411    // Protect against breakage in non-browser environments and check global autoSetup option.
1412    if (!isReal() || videojs.options.autoSetup === false) {
1413      return;
1414    }
1415
1416    var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
1417    var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
1418    var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
1419    var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
1420
1421    if (mediaEls && mediaEls.length > 0) {
1422      for (var i = 0, e = mediaEls.length; i < e; i++) {
1423        var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
1424
1425        if (mediaEl && mediaEl.getAttribute) {
1426          // Make sure this player hasn't already been set up.
1427          if (mediaEl.player === undefined) {
1428            var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
1429            // We only auto-setup if they've added the data-setup attr.
1430
1431            if (options !== null) {
1432              // Create new video.js instance.
1433              videojs(mediaEl);
1434            }
1435          } // If getAttribute isn't defined, we need to wait for the DOM.
1436
1437        } else {
1438          autoSetupTimeout(1);
1439          break;
1440        }
1441      } // No videos were found, so keep looping unless page is finished loading.
1442
1443    } else if (!_windowLoaded) {
1444      autoSetupTimeout(1);
1445    }
1446  };
1447  /**
1448   * Wait until the page is loaded before running autoSetup. This will be called in
1449   * autoSetup if `hasLoaded` returns false.
1450   *
1451   * @param {number} wait
1452   *        How long to wait in ms
1453   *
1454   * @param {module:videojs} [vjs]
1455   *        The videojs library function
1456   */
1457
1458
1459  function autoSetupTimeout(wait, vjs) {
1460    if (vjs) {
1461      videojs = vjs;
1462    }
1463
1464    window$1.setTimeout(autoSetup, wait);
1465  }
1466  /**
1467   * Used to set the internal tracking of window loaded state to true.
1468   *
1469   * @private
1470   */
1471
1472
1473  function setWindowLoaded() {
1474    _windowLoaded = true;
1475    window$1.removeEventListener('load', setWindowLoaded);
1476  }
1477
1478  if (isReal()) {
1479    if (document.readyState === 'complete') {
1480      setWindowLoaded();
1481    } else {
1482      /**
1483       * Listen for the load event on window, and set _windowLoaded to true.
1484       *
1485       * We use a standard event listener here to avoid incrementing the GUID
1486       * before any players are created.
1487       *
1488       * @listens load
1489       */
1490      window$1.addEventListener('load', setWindowLoaded);
1491    }
1492  }
1493
1494  /**
1495   * @file stylesheet.js
1496   * @module stylesheet
1497   */
1498  /**
1499   * Create a DOM syle element given a className for it.
1500   *
1501   * @param {string} className
1502   *        The className to add to the created style element.
1503   *
1504   * @return {Element}
1505   *         The element that was created.
1506   */
1507
1508  var createStyleElement = function createStyleElement(className) {
1509    var style = document.createElement('style');
1510    style.className = className;
1511    return style;
1512  };
1513  /**
1514   * Add text to a DOM element.
1515   *
1516   * @param {Element} el
1517   *        The Element to add text content to.
1518   *
1519   * @param {string} content
1520   *        The text to add to the element.
1521   */
1522
1523  var setTextContent = function setTextContent(el, content) {
1524    if (el.styleSheet) {
1525      el.styleSheet.cssText = content;
1526    } else {
1527      el.textContent = content;
1528    }
1529  };
1530
1531  /**
1532   * @file guid.js
1533   * @module guid
1534   */
1535  // Default value for GUIDs. This allows us to reset the GUID counter in tests.
1536  //
1537  // The initial GUID is 3 because some users have come to rely on the first
1538  // default player ID ending up as `vjs_video_3`.
1539  //
1540  // See: https://github.com/videojs/video.js/pull/6216
1541  var _initialGuid = 3;
1542  /**
1543   * Unique ID for an element or function
1544   *
1545   * @type {Number}
1546   */
1547
1548  var _guid = _initialGuid;
1549  /**
1550   * Get a unique auto-incrementing ID by number that has not been returned before.
1551   *
1552   * @return {number}
1553   *         A new unique ID.
1554   */
1555
1556  function newGUID() {
1557    return _guid++;
1558  }
1559
1560  /**
1561   * @file dom-data.js
1562   * @module dom-data
1563   */
1564  var FakeWeakMap;
1565
1566  if (!window$1.WeakMap) {
1567    FakeWeakMap = /*#__PURE__*/function () {
1568      function FakeWeakMap() {
1569        this.vdata = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
1570        this.data = {};
1571      }
1572
1573      var _proto = FakeWeakMap.prototype;
1574
1575      _proto.set = function set(key, value) {
1576        var access = key[this.vdata] || newGUID();
1577
1578        if (!key[this.vdata]) {
1579          key[this.vdata] = access;
1580        }
1581
1582        this.data[access] = value;
1583        return this;
1584      };
1585
1586      _proto.get = function get(key) {
1587        var access = key[this.vdata]; // we have data, return it
1588
1589        if (access) {
1590          return this.data[access];
1591        } // we don't have data, return nothing.
1592        // return undefined explicitly as that's the contract for this method
1593
1594
1595        log('We have no data for this element', key);
1596        return undefined;
1597      };
1598
1599      _proto.has = function has(key) {
1600        var access = key[this.vdata];
1601        return access in this.data;
1602      };
1603
1604      _proto["delete"] = function _delete(key) {
1605        var access = key[this.vdata];
1606
1607        if (access) {
1608          delete this.data[access];
1609          delete key[this.vdata];
1610        }
1611      };
1612
1613      return FakeWeakMap;
1614    }();
1615  }
1616  /**
1617   * Element Data Store.
1618   *
1619   * Allows for binding data to an element without putting it directly on the
1620   * element. Ex. Event listeners are stored here.
1621   * (also from jsninja.com, slightly modified and updated for closure compiler)
1622   *
1623   * @type {Object}
1624   * @private
1625   */
1626
1627
1628  var DomData = window$1.WeakMap ? new WeakMap() : new FakeWeakMap();
1629
1630  /**
1631   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1632   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1633   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1634   * robust as jquery's, so there's probably some differences.
1635   *
1636   * @file events.js
1637   * @module events
1638   */
1639  /**
1640   * Clean up the listener cache and dispatchers
1641   *
1642   * @param {Element|Object} elem
1643   *        Element to clean up
1644   *
1645   * @param {string} type
1646   *        Type of event to clean up
1647   */
1648
1649  function _cleanUpEvents(elem, type) {
1650    if (!DomData.has(elem)) {
1651      return;
1652    }
1653
1654    var data = DomData.get(elem); // Remove the events of a particular type if there are none left
1655
1656    if (data.handlers[type].length === 0) {
1657      delete data.handlers[type]; // data.handlers[type] = null;
1658      // Setting to null was causing an error with data.handlers
1659      // Remove the meta-handler from the element
1660
1661      if (elem.removeEventListener) {
1662        elem.removeEventListener(type, data.dispatcher, false);
1663      } else if (elem.detachEvent) {
1664        elem.detachEvent('on' + type, data.dispatcher);
1665      }
1666    } // Remove the events object if there are no types left
1667
1668
1669    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1670      delete data.handlers;
1671      delete data.dispatcher;
1672      delete data.disabled;
1673    } // Finally remove the element data if there is no data left
1674
1675
1676    if (Object.getOwnPropertyNames(data).length === 0) {
1677      DomData["delete"](elem);
1678    }
1679  }
1680  /**
1681   * Loops through an array of event types and calls the requested method for each type.
1682   *
1683   * @param {Function} fn
1684   *        The event method we want to use.
1685   *
1686   * @param {Element|Object} elem
1687   *        Element or object to bind listeners to
1688   *
1689   * @param {string} type
1690   *        Type of event to bind to.
1691   *
1692   * @param {EventTarget~EventListener} callback
1693   *        Event listener.
1694   */
1695
1696
1697  function _handleMultipleEvents(fn, elem, types, callback) {
1698    types.forEach(function (type) {
1699      // Call the event method for each one of the types
1700      fn(elem, type, callback);
1701    });
1702  }
1703  /**
1704   * Fix a native event to have standard property values
1705   *
1706   * @param {Object} event
1707   *        Event object to fix.
1708   *
1709   * @return {Object}
1710   *         Fixed event object.
1711   */
1712
1713
1714  function fixEvent(event) {
1715    if (event.fixed_) {
1716      return event;
1717    }
1718
1719    function returnTrue() {
1720      return true;
1721    }
1722
1723    function returnFalse() {
1724      return false;
1725    } // Test if fixing up is needed
1726    // Used to check if !event.stopPropagation instead of isPropagationStopped
1727    // But native events return true for stopPropagation, but don't have
1728    // other expected methods like isPropagationStopped. Seems to be a problem
1729    // with the Javascript Ninja code. So we're just overriding all events now.
1730
1731
1732    if (!event || !event.isPropagationStopped) {
1733      var old = event || window$1.event;
1734      event = {}; // Clone the old object so that we can modify the values event = {};
1735      // IE8 Doesn't like when you mess with native event properties
1736      // Firefox returns false for event.hasOwnProperty('type') and other props
1737      //  which makes copying more difficult.
1738      // TODO: Probably best to create a whitelist of event props
1739
1740      for (var key in old) {
1741        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
1742        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
1743        // and webkitMovementX/Y
1744        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
1745          // Chrome 32+ warns if you try to copy deprecated returnValue, but
1746          // we still want to if preventDefault isn't supported (IE8).
1747          if (!(key === 'returnValue' && old.preventDefault)) {
1748            event[key] = old[key];
1749          }
1750        }
1751      } // The event occurred on this element
1752
1753
1754      if (!event.target) {
1755        event.target = event.srcElement || document;
1756      } // Handle which other element the event is related to
1757
1758
1759      if (!event.relatedTarget) {
1760        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1761      } // Stop the default browser action
1762
1763
1764      event.preventDefault = function () {
1765        if (old.preventDefault) {
1766          old.preventDefault();
1767        }
1768
1769        event.returnValue = false;
1770        old.returnValue = false;
1771        event.defaultPrevented = true;
1772      };
1773
1774      event.defaultPrevented = false; // Stop the event from bubbling
1775
1776      event.stopPropagation = function () {
1777        if (old.stopPropagation) {
1778          old.stopPropagation();
1779        }
1780
1781        event.cancelBubble = true;
1782        old.cancelBubble = true;
1783        event.isPropagationStopped = returnTrue;
1784      };
1785
1786      event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
1787
1788      event.stopImmediatePropagation = function () {
1789        if (old.stopImmediatePropagation) {
1790          old.stopImmediatePropagation();
1791        }
1792
1793        event.isImmediatePropagationStopped = returnTrue;
1794        event.stopPropagation();
1795      };
1796
1797      event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
1798
1799      if (event.clientX !== null && event.clientX !== undefined) {
1800        var doc = document.documentElement;
1801        var body = document.body;
1802        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1803        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
1804      } // Handle key presses
1805
1806
1807      event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
1808      // 0 == left; 1 == middle; 2 == right
1809
1810      if (event.button !== null && event.button !== undefined) {
1811        // The following is disabled because it does not pass videojs-standard
1812        // and... yikes.
1813
1814        /* eslint-disable */
1815        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
1816        /* eslint-enable */
1817      }
1818    }
1819
1820    event.fixed_ = true; // Returns fixed-up instance
1821
1822    return event;
1823  }
1824  /**
1825   * Whether passive event listeners are supported
1826   */
1827
1828  var _supportsPassive;
1829
1830  var supportsPassive = function supportsPassive() {
1831    if (typeof _supportsPassive !== 'boolean') {
1832      _supportsPassive = false;
1833
1834      try {
1835        var opts = Object.defineProperty({}, 'passive', {
1836          get: function get() {
1837            _supportsPassive = true;
1838          }
1839        });
1840        window$1.addEventListener('test', null, opts);
1841        window$1.removeEventListener('test', null, opts);
1842      } catch (e) {// disregard
1843      }
1844    }
1845
1846    return _supportsPassive;
1847  };
1848  /**
1849   * Touch events Chrome expects to be passive
1850   */
1851
1852
1853  var passiveEvents = ['touchstart', 'touchmove'];
1854  /**
1855   * Add an event listener to element
1856   * It stores the handler function in a separate cache object
1857   * and adds a generic handler to the element's event,
1858   * along with a unique id (guid) to the element.
1859   *
1860   * @param {Element|Object} elem
1861   *        Element or object to bind listeners to
1862   *
1863   * @param {string|string[]} type
1864   *        Type of event to bind to.
1865   *
1866   * @param {EventTarget~EventListener} fn
1867   *        Event listener.
1868   */
1869
1870  function on(elem, type, fn) {
1871    if (Array.isArray(type)) {
1872      return _handleMultipleEvents(on, elem, type, fn);
1873    }
1874
1875    if (!DomData.has(elem)) {
1876      DomData.set(elem, {});
1877    }
1878
1879    var data = DomData.get(elem); // We need a place to store all our handler data
1880
1881    if (!data.handlers) {
1882      data.handlers = {};
1883    }
1884
1885    if (!data.handlers[type]) {
1886      data.handlers[type] = [];
1887    }
1888
1889    if (!fn.guid) {
1890      fn.guid = newGUID();
1891    }
1892
1893    data.handlers[type].push(fn);
1894
1895    if (!data.dispatcher) {
1896      data.disabled = false;
1897
1898      data.dispatcher = function (event, hash) {
1899        if (data.disabled) {
1900          return;
1901        }
1902
1903        event = fixEvent(event);
1904        var handlers = data.handlers[event.type];
1905
1906        if (handlers) {
1907          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1908          var handlersCopy = handlers.slice(0);
1909
1910          for (var m = 0, n = handlersCopy.length; m < n; m++) {
1911            if (event.isImmediatePropagationStopped()) {
1912              break;
1913            } else {
1914              try {
1915                handlersCopy[m].call(elem, event, hash);
1916              } catch (e) {
1917                log.error(e);
1918              }
1919            }
1920          }
1921        }
1922      };
1923    }
1924
1925    if (data.handlers[type].length === 1) {
1926      if (elem.addEventListener) {
1927        var options = false;
1928
1929        if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
1930          options = {
1931            passive: true
1932          };
1933        }
1934
1935        elem.addEventListener(type, data.dispatcher, options);
1936      } else if (elem.attachEvent) {
1937        elem.attachEvent('on' + type, data.dispatcher);
1938      }
1939    }
1940  }
1941  /**
1942   * Removes event listeners from an element
1943   *
1944   * @param {Element|Object} elem
1945   *        Object to remove listeners from.
1946   *
1947   * @param {string|string[]} [type]
1948   *        Type of listener to remove. Don't include to remove all events from element.
1949   *
1950   * @param {EventTarget~EventListener} [fn]
1951   *        Specific listener to remove. Don't include to remove listeners for an event
1952   *        type.
1953   */
1954
1955  function off(elem, type, fn) {
1956    // Don't want to add a cache object through getElData if not needed
1957    if (!DomData.has(elem)) {
1958      return;
1959    }
1960
1961    var data = DomData.get(elem); // If no events exist, nothing to unbind
1962
1963    if (!data.handlers) {
1964      return;
1965    }
1966
1967    if (Array.isArray(type)) {
1968      return _handleMultipleEvents(off, elem, type, fn);
1969    } // Utility function
1970
1971
1972    var removeType = function removeType(el, t) {
1973      data.handlers[t] = [];
1974
1975      _cleanUpEvents(el, t);
1976    }; // Are we removing all bound events?
1977
1978
1979    if (type === undefined) {
1980      for (var t in data.handlers) {
1981        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
1982          removeType(elem, t);
1983        }
1984      }
1985
1986      return;
1987    }
1988
1989    var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
1990
1991    if (!handlers) {
1992      return;
1993    } // If no listener was provided, remove all listeners for type
1994
1995
1996    if (!fn) {
1997      removeType(elem, type);
1998      return;
1999    } // We're only removing a single handler
2000
2001
2002    if (fn.guid) {
2003      for (var n = 0; n < handlers.length; n++) {
2004        if (handlers[n].guid === fn.guid) {
2005          handlers.splice(n--, 1);
2006        }
2007      }
2008    }
2009
2010    _cleanUpEvents(elem, type);
2011  }
2012  /**
2013   * Trigger an event for an element
2014   *
2015   * @param {Element|Object} elem
2016   *        Element to trigger an event on
2017   *
2018   * @param {EventTarget~Event|string} event
2019   *        A string (the type) or an event object with a type attribute
2020   *
2021   * @param {Object} [hash]
2022   *        data hash to pass along with the event
2023   *
2024   * @return {boolean|undefined}
2025   *         Returns the opposite of `defaultPrevented` if default was
2026   *         prevented. Otherwise, returns `undefined`
2027   */
2028
2029  function trigger(elem, event, hash) {
2030    // Fetches element data and a reference to the parent (for bubbling).
2031    // Don't want to add a data object to cache for every parent,
2032    // so checking hasElData first.
2033    var elemData = DomData.has(elem) ? DomData.get(elem) : {};
2034    var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
2035    // handler;
2036    // If an event name was passed as a string, creates an event out of it
2037
2038    if (typeof event === 'string') {
2039      event = {
2040        type: event,
2041        target: elem
2042      };
2043    } else if (!event.target) {
2044      event.target = elem;
2045    } // Normalizes the event properties.
2046
2047
2048    event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
2049
2050    if (elemData.dispatcher) {
2051      elemData.dispatcher.call(elem, event, hash);
2052    } // Unless explicitly stopped or the event does not bubble (e.g. media events)
2053    // recursively calls this function to bubble the event up the DOM.
2054
2055
2056    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2057      trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
2058    } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2059      if (!DomData.has(event.target)) {
2060        DomData.set(event.target, {});
2061      }
2062
2063      var targetData = DomData.get(event.target); // Checks if the target has a default action for this event.
2064
2065      if (event.target[event.type]) {
2066        // Temporarily disables event dispatching on the target as we have already executed the handler.
2067        targetData.disabled = true; // Executes the default action.
2068
2069        if (typeof event.target[event.type] === 'function') {
2070          event.target[event.type]();
2071        } // Re-enables event dispatching.
2072
2073
2074        targetData.disabled = false;
2075      }
2076    } // Inform the triggerer if the default was prevented by returning false
2077
2078
2079    return !event.defaultPrevented;
2080  }
2081  /**
2082   * Trigger a listener only once for an event.
2083   *
2084   * @param {Element|Object} elem
2085   *        Element or object to bind to.
2086   *
2087   * @param {string|string[]} type
2088   *        Name/type of event
2089   *
2090   * @param {Event~EventListener} fn
2091   *        Event listener function
2092   */
2093
2094  function one(elem, type, fn) {
2095    if (Array.isArray(type)) {
2096      return _handleMultipleEvents(one, elem, type, fn);
2097    }
2098
2099    var func = function func() {
2100      off(elem, type, func);
2101      fn.apply(this, arguments);
2102    }; // copy the guid to the new function so it can removed using the original function's ID
2103
2104
2105    func.guid = fn.guid = fn.guid || newGUID();
2106    on(elem, type, func);
2107  }
2108  /**
2109   * Trigger a listener only once and then turn if off for all
2110   * configured events
2111   *
2112   * @param {Element|Object} elem
2113   *        Element or object to bind to.
2114   *
2115   * @param {string|string[]} type
2116   *        Name/type of event
2117   *
2118   * @param {Event~EventListener} fn
2119   *        Event listener function
2120   */
2121
2122  function any(elem, type, fn) {
2123    var func = function func() {
2124      off(elem, type, func);
2125      fn.apply(this, arguments);
2126    }; // copy the guid to the new function so it can removed using the original function's ID
2127
2128
2129    func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything
2130
2131    on(elem, type, func);
2132  }
2133
2134  var Events = /*#__PURE__*/Object.freeze({
2135    __proto__: null,
2136    fixEvent: fixEvent,
2137    on: on,
2138    off: off,
2139    trigger: trigger,
2140    one: one,
2141    any: any
2142  });
2143
2144  /**
2145   * @file fn.js
2146   * @module fn
2147   */
2148  var UPDATE_REFRESH_INTERVAL = 30;
2149  /**
2150   * Bind (a.k.a proxy or context). A simple method for changing the context of
2151   * a function.
2152   *
2153   * It also stores a unique id on the function so it can be easily removed from
2154   * events.
2155   *
2156   * @function
2157   * @param    {Mixed} context
2158   *           The object to bind as scope.
2159   *
2160   * @param    {Function} fn
2161   *           The function to be bound to a scope.
2162   *
2163   * @param    {number} [uid]
2164   *           An optional unique ID for the function to be set
2165   *
2166   * @return   {Function}
2167   *           The new function that will be bound into the context given
2168   */
2169
2170  var bind = function bind(context, fn, uid) {
2171    // Make sure the function has a unique ID
2172    if (!fn.guid) {
2173      fn.guid = newGUID();
2174    } // Create the new function that changes the context
2175
2176
2177    var bound = fn.bind(context); // Allow for the ability to individualize this function
2178    // Needed in the case where multiple objects might share the same prototype
2179    // IF both items add an event listener with the same function, then you try to remove just one
2180    // it will remove both because they both have the same guid.
2181    // when using this, you need to use the bind method when you remove the listener as well.
2182    // currently used in text tracks
2183
2184    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2185    return bound;
2186  };
2187  /**
2188   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2189   * at most once per every `wait` milliseconds.
2190   *
2191   * @function
2192   * @param    {Function} fn
2193   *           The function to be throttled.
2194   *
2195   * @param    {number}   wait
2196   *           The number of milliseconds by which to throttle.
2197   *
2198   * @return   {Function}
2199   */
2200
2201  var throttle = function throttle(fn, wait) {
2202    var last = window$1.performance.now();
2203
2204    var throttled = function throttled() {
2205      var now = window$1.performance.now();
2206
2207      if (now - last >= wait) {
2208        fn.apply(void 0, arguments);
2209        last = now;
2210      }
2211    };
2212
2213    return throttled;
2214  };
2215  /**
2216   * Creates a debounced function that delays invoking `func` until after `wait`
2217   * milliseconds have elapsed since the last time the debounced function was
2218   * invoked.
2219   *
2220   * Inspired by lodash and underscore implementations.
2221   *
2222   * @function
2223   * @param    {Function} func
2224   *           The function to wrap with debounce behavior.
2225   *
2226   * @param    {number} wait
2227   *           The number of milliseconds to wait after the last invocation.
2228   *
2229   * @param    {boolean} [immediate]
2230   *           Whether or not to invoke the function immediately upon creation.
2231   *
2232   * @param    {Object} [context=window]
2233   *           The "context" in which the debounced function should debounce. For
2234   *           example, if this function should be tied to a Video.js player,
2235   *           the player can be passed here. Alternatively, defaults to the
2236   *           global `window` object.
2237   *
2238   * @return   {Function}
2239   *           A debounced function.
2240   */
2241
2242  var debounce = function debounce(func, wait, immediate, context) {
2243    if (context === void 0) {
2244      context = window$1;
2245    }
2246
2247    var timeout;
2248
2249    var cancel = function cancel() {
2250      context.clearTimeout(timeout);
2251      timeout = null;
2252    };
2253    /* eslint-disable consistent-this */
2254
2255
2256    var debounced = function debounced() {
2257      var self = this;
2258      var args = arguments;
2259
2260      var _later = function later() {
2261        timeout = null;
2262        _later = null;
2263
2264        if (!immediate) {
2265          func.apply(self, args);
2266        }
2267      };
2268
2269      if (!timeout && immediate) {
2270        func.apply(self, args);
2271      }
2272
2273      context.clearTimeout(timeout);
2274      timeout = context.setTimeout(_later, wait);
2275    };
2276    /* eslint-enable consistent-this */
2277
2278
2279    debounced.cancel = cancel;
2280    return debounced;
2281  };
2282
2283  /**
2284   * @file src/js/event-target.js
2285   */
2286  /**
2287   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2288   * adds shorthand functions that wrap around lengthy functions. For example:
2289   * the `on` function is a wrapper around `addEventListener`.
2290   *
2291   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2292   * @class EventTarget
2293   */
2294
2295  var EventTarget = function EventTarget() {};
2296  /**
2297   * A Custom DOM event.
2298   *
2299   * @typedef {Object} EventTarget~Event
2300   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2301   */
2302
2303  /**
2304   * All event listeners should follow the following format.
2305   *
2306   * @callback EventTarget~EventListener
2307   * @this {EventTarget}
2308   *
2309   * @param {EventTarget~Event} event
2310   *        the event that triggered this function
2311   *
2312   * @param {Object} [hash]
2313   *        hash of data sent during the event
2314   */
2315
2316  /**
2317   * An object containing event names as keys and booleans as values.
2318   *
2319   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2320   *         will have extra functionality. See that function for more information.
2321   *
2322   * @property EventTarget.prototype.allowedEvents_
2323   * @private
2324   */
2325
2326
2327  EventTarget.prototype.allowedEvents_ = {};
2328  /**
2329   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2330   * function that will get called when an event with a certain name gets triggered.
2331   *
2332   * @param {string|string[]} type
2333   *        An event name or an array of event names.
2334   *
2335   * @param {EventTarget~EventListener} fn
2336   *        The function to call with `EventTarget`s
2337   */
2338
2339  EventTarget.prototype.on = function (type, fn) {
2340    // Remove the addEventListener alias before calling Events.on
2341    // so we don't get into an infinite type loop
2342    var ael = this.addEventListener;
2343
2344    this.addEventListener = function () {};
2345
2346    on(this, type, fn);
2347    this.addEventListener = ael;
2348  };
2349  /**
2350   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2351   * the standard DOM API.
2352   *
2353   * @function
2354   * @see {@link EventTarget#on}
2355   */
2356
2357
2358  EventTarget.prototype.addEventListener = EventTarget.prototype.on;
2359  /**
2360   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2361   * This makes it so that the `event listener` will no longer get called when the
2362   * named event happens.
2363   *
2364   * @param {string|string[]} type
2365   *        An event name or an array of event names.
2366   *
2367   * @param {EventTarget~EventListener} fn
2368   *        The function to remove.
2369   */
2370
2371  EventTarget.prototype.off = function (type, fn) {
2372    off(this, type, fn);
2373  };
2374  /**
2375   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2376   * the standard DOM API.
2377   *
2378   * @function
2379   * @see {@link EventTarget#off}
2380   */
2381
2382
2383  EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
2384  /**
2385   * This function will add an `event listener` that gets triggered only once. After the
2386   * first trigger it will get removed. This is like adding an `event listener`
2387   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2388   *
2389   * @param {string|string[]} type
2390   *        An event name or an array of event names.
2391   *
2392   * @param {EventTarget~EventListener} fn
2393   *        The function to be called once for each event name.
2394   */
2395
2396  EventTarget.prototype.one = function (type, fn) {
2397    // Remove the addEventListener aliasing Events.on
2398    // so we don't get into an infinite type loop
2399    var ael = this.addEventListener;
2400
2401    this.addEventListener = function () {};
2402
2403    one(this, type, fn);
2404    this.addEventListener = ael;
2405  };
2406
2407  EventTarget.prototype.any = function (type, fn) {
2408    // Remove the addEventListener aliasing Events.on
2409    // so we don't get into an infinite type loop
2410    var ael = this.addEventListener;
2411
2412    this.addEventListener = function () {};
2413
2414    any(this, type, fn);
2415    this.addEventListener = ael;
2416  };
2417  /**
2418   * This function causes an event to happen. This will then cause any `event listeners`
2419   * that are waiting for that event, to get called. If there are no `event listeners`
2420   * for an event then nothing will happen.
2421   *
2422   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2423   * Trigger will also call the `on` + `uppercaseEventName` function.
2424   *
2425   * Example:
2426   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2427   * `onClick` if it exists.
2428   *
2429   * @param {string|EventTarget~Event|Object} event
2430   *        The name of the event, an `Event`, or an object with a key of type set to
2431   *        an event name.
2432   */
2433
2434
2435  EventTarget.prototype.trigger = function (event) {
2436    var type = event.type || event; // deprecation
2437    // In a future version we should default target to `this`
2438    // similar to how we default the target to `elem` in
2439    // `Events.trigger`. Right now the default `target` will be
2440    // `document` due to the `Event.fixEvent` call.
2441
2442    if (typeof event === 'string') {
2443      event = {
2444        type: type
2445      };
2446    }
2447
2448    event = fixEvent(event);
2449
2450    if (this.allowedEvents_[type] && this['on' + type]) {
2451      this['on' + type](event);
2452    }
2453
2454    trigger(this, event);
2455  };
2456  /**
2457   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2458   * the standard DOM API.
2459   *
2460   * @function
2461   * @see {@link EventTarget#trigger}
2462   */
2463
2464
2465  EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
2466  var EVENT_MAP;
2467
2468  EventTarget.prototype.queueTrigger = function (event) {
2469    var _this = this;
2470
2471    // only set up EVENT_MAP if it'll be used
2472    if (!EVENT_MAP) {
2473      EVENT_MAP = new Map();
2474    }
2475
2476    var type = event.type || event;
2477    var map = EVENT_MAP.get(this);
2478
2479    if (!map) {
2480      map = new Map();
2481      EVENT_MAP.set(this, map);
2482    }
2483
2484    var oldTimeout = map.get(type);
2485    map["delete"](type);
2486    window$1.clearTimeout(oldTimeout);
2487    var timeout = window$1.setTimeout(function () {
2488      // if we cleared out all timeouts for the current target, delete its map
2489      if (map.size === 0) {
2490        map = null;
2491        EVENT_MAP["delete"](_this);
2492      }
2493
2494      _this.trigger(event);
2495    }, 0);
2496    map.set(type, timeout);
2497  };
2498
2499  /**
2500   * @file mixins/evented.js
2501   * @module evented
2502   */
2503  /**
2504   * Returns whether or not an object has had the evented mixin applied.
2505   *
2506   * @param  {Object} object
2507   *         An object to test.
2508   *
2509   * @return {boolean}
2510   *         Whether or not the object appears to be evented.
2511   */
2512
2513  var isEvented = function isEvented(object) {
2514    return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2515      return typeof object[k] === 'function';
2516    });
2517  };
2518  /**
2519   * Adds a callback to run after the evented mixin applied.
2520   *
2521   * @param  {Object} object
2522   *         An object to Add
2523   * @param  {Function} callback
2524   *         The callback to run.
2525   */
2526
2527
2528  var addEventedCallback = function addEventedCallback(target, callback) {
2529    if (isEvented(target)) {
2530      callback();
2531    } else {
2532      if (!target.eventedCallbacks) {
2533        target.eventedCallbacks = [];
2534      }
2535
2536      target.eventedCallbacks.push(callback);
2537    }
2538  };
2539  /**
2540   * Whether a value is a valid event type - non-empty string or array.
2541   *
2542   * @private
2543   * @param  {string|Array} type
2544   *         The type value to test.
2545   *
2546   * @return {boolean}
2547   *         Whether or not the type is a valid event type.
2548   */
2549
2550
2551  var isValidEventType = function isValidEventType(type) {
2552    return (// The regex here verifies that the `type` contains at least one non-
2553      // whitespace character.
2554      typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
2555    );
2556  };
2557  /**
2558   * Validates a value to determine if it is a valid event target. Throws if not.
2559   *
2560   * @private
2561   * @throws {Error}
2562   *         If the target does not appear to be a valid event target.
2563   *
2564   * @param  {Object} target
2565   *         The object to test.
2566   */
2567
2568
vendor: 6,295 bytes, lines 2569-2762
2569  var validateTarget = function validateTarget(target) {
2570    if (!target.nodeName && !isEvented(target)) {
2571      throw new Error('Invalid target; must be a DOM node or evented object.');
2572    }
2573  };
2574  /**
2575   * Validates a value to determine if it is a valid event target. Throws if not.
2576   *
2577   * @private
2578   * @throws {Error}
2579   *         If the type does not appear to be a valid event type.
2580   *
2581   * @param  {string|Array} type
2582   *         The type to test.
2583   */
2584
2585
2586  var validateEventType = function validateEventType(type) {
2587    if (!isValidEventType(type)) {
2588      throw new Error('Invalid event type; must be a non-empty string or array.');
2589    }
2590  };
2591  /**
2592   * Validates a value to determine if it is a valid listener. Throws if not.
2593   *
2594   * @private
2595   * @throws {Error}
2596   *         If the listener is not a function.
2597   *
2598   * @param  {Function} listener
2599   *         The listener to test.
2600   */
2601
2602
2603  var validateListener = function validateListener(listener) {
2604    if (typeof listener !== 'function') {
2605      throw new Error('Invalid listener; must be a function.');
2606    }
2607  };
2608  /**
2609   * Takes an array of arguments given to `on()` or `one()`, validates them, and
2610   * normalizes them into an object.
2611   *
2612   * @private
2613   * @param  {Object} self
2614   *         The evented object on which `on()` or `one()` was called. This
2615   *         object will be bound as the `this` value for the listener.
2616   *
2617   * @param  {Array} args
2618   *         An array of arguments passed to `on()` or `one()`.
2619   *
2620   * @return {Object}
2621   *         An object containing useful values for `on()` or `one()` calls.
2622   */
2623
2624
2625  var normalizeListenArgs = function normalizeListenArgs(self, args) {
2626    // If the number of arguments is less than 3, the target is always the
2627    // evented object itself.
2628    var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2629    var target;
2630    var type;
2631    var listener;
2632
2633    if (isTargetingSelf) {
2634      target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
2635      // the evented object itself.
2636
2637      if (args.length >= 3) {
2638        args.shift();
2639      }
2640
2641      type = args[0];
2642      listener = args[1];
2643    } else {
2644      target = args[0];
2645      type = args[1];
2646      listener = args[2];
2647    }
2648
2649    validateTarget(target);
2650    validateEventType(type);
2651    validateListener(listener);
2652    listener = bind(self, listener);
2653    return {
2654      isTargetingSelf: isTargetingSelf,
2655      target: target,
2656      type: type,
2657      listener: listener
2658    };
2659  };
2660  /**
2661   * Adds the listener to the event type(s) on the target, normalizing for
2662   * the type of target.
2663   *
2664   * @private
2665   * @param  {Element|Object} target
2666   *         A DOM node or evented object.
2667   *
2668   * @param  {string} method
2669   *         The event binding method to use ("on" or "one").
2670   *
2671   * @param  {string|Array} type
2672   *         One or more event type(s).
2673   *
2674   * @param  {Function} listener
2675   *         A listener function.
2676   */
2677
2678
2679  var listen = function listen(target, method, type, listener) {
2680    validateTarget(target);
2681
2682    if (target.nodeName) {
2683      Events[method](target, type, listener);
2684    } else {
2685      target[method](type, listener);
2686    }
2687  };
2688  /**
2689   * Contains methods that provide event capabilities to an object which is passed
2690   * to {@link module:evented|evented}.
2691   *
2692   * @mixin EventedMixin
2693   */
2694
2695
2696  var EventedMixin = {
2697    /**
2698     * Add a listener to an event (or events) on this object or another evented
2699     * object.
2700     *
2701     * @param  {string|Array|Element|Object} targetOrType
2702     *         If this is a string or array, it represents the event type(s)
2703     *         that will trigger the listener.
2704     *
2705     *         Another evented object can be passed here instead, which will
2706     *         cause the listener to listen for events on _that_ object.
2707     *
2708     *         In either case, the listener's `this` value will be bound to
2709     *         this object.
2710     *
2711     * @param  {string|Array|Function} typeOrListener
2712     *         If the first argument was a string or array, this should be the
2713     *         listener function. Otherwise, this is a string or array of event
2714     *         type(s).
2715     *
2716     * @param  {Function} [listener]
2717     *         If the first argument was another evented object, this will be
2718     *         the listener function.
2719     */
2720    on: function on() {
2721      var _this = this;
2722
2723      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
2724        args[_key] = arguments[_key];
2725      }
2726
2727      var _normalizeListenArgs = normalizeListenArgs(this, args),
2728          isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
2729          target = _normalizeListenArgs.target,
2730          type = _normalizeListenArgs.type,
2731          listener = _normalizeListenArgs.listener;
2732
2733      listen(target, 'on', type, listener); // If this object is listening to another evented object.
2734
2735      if (!isTargetingSelf) {
2736        // If this object is disposed, remove the listener.
2737        var removeListenerOnDispose = function removeListenerOnDispose() {
2738          return _this.off(target, type, listener);
2739        }; // Use the same function ID as the listener so we can remove it later it
2740        // using the ID of the original listener.
2741
2742
2743        removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
2744        // that if the target is disposed BEFORE this object, we remove the
2745        // removal listener that was just added. Otherwise, we create a memory leak.
2746
2747        var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
2748          return _this.off('dispose', removeListenerOnDispose);
2749        }; // Use the same function ID as the listener so we can remove it later
2750        // it using the ID of the original listener.
2751
2752
2753        removeRemoverOnTargetDispose.guid = listener.guid;
2754        listen(this, 'on', 'dispose', removeListenerOnDispose);
2755        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
2756      }
2757    },
2758
2759    /**
2760     * Add a listener to an event (or events) on this object or another evented
2761     * object. The listener will be called once per event and then removed.
2762     *
2763     * @param  {string|Array|Element|Object} targetOrType
2764     *         If this is a string or array, it represents the event type(s)
2765     *         that will trigger the listener.
2766     *
2767     *         Another evented object can be passed here instead, which will
2768     *         cause the listener to listen for events on _that_ object.
2769     *
2770     *         In either case, the listener's `this` value will be bound to
2771     *         this object.
2772     *
2773     * @param  {string|Array|Function} typeOrListener
2774     *         If the first argument was a string or array, this should be the
2775     *         listener function. Otherwise, this is a string or array of event
2776     *         type(s).
2777     *
2778     * @param  {Function} [listener]
2779     *         If the first argument was another evented object, this will be
2780     *         the listener function.
2781     */
2782    one: function one() {
2783      var _this2 = this;
2784
2785      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
2786        args[_key2] = arguments[_key2];
2787      }
2788
2789      var _normalizeListenArgs2 = normalizeListenArgs(this, args),
2790          isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
2791          target = _normalizeListenArgs2.target,
2792          type = _normalizeListenArgs2.type,
2793          listener = _normalizeListenArgs2.listener; // Targeting this evented object.
2794
2795
2796      if (isTargetingSelf) {
2797        listen(target, 'one', type, listener); // Targeting another evented object.
2798      } else {
2799        // TODO: This wrapper is incorrect! It should only
2800        //       remove the wrapper for the event type that called it.
2801        //       Instead all listners are removed on the first trigger!
2802        //       see https://github.com/videojs/video.js/issues/5962
2803        var wrapper = function wrapper() {
2804          _this2.off(target, type, wrapper);
2805
2806          for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
2807            largs[_key3] = arguments[_key3];
2808          }
2809
2810          listener.apply(null, largs);
2811        }; // Use the same function ID as the listener so we can remove it later
2812        // it using the ID of the original listener.
2813
2814
2815        wrapper.guid = listener.guid;
2816        listen(target, 'one', type, wrapper);
2817      }
2818    },
2819
2820    /**
2821     * Add a listener to an event (or events) on this object or another evented
2822     * object. The listener will only be called once for the first event that is triggered
2823     * then removed.
2824     *
2825     * @param  {string|Array|Element|Object} targetOrType
2826     *         If this is a string or array, it represents the event type(s)
2827     *         that will trigger the listener.
2828     *
2829     *         Another evented object can be passed here instead, which will
2830     *         cause the listener to listen for events on _that_ object.
2831     *
2832     *         In either case, the listener's `this` value will be bound to
2833     *         this object.
2834     *
2835     * @param  {string|Array|Function} typeOrListener
2836     *         If the first argument was a string or array, this should be the
2837     *         listener function. Otherwise, this is a string or array of event
2838     *         type(s).
2839     *
2840     * @param  {Function} [listener]
2841     *         If the first argument was another evented object, this will be
2842     *         the listener function.
2843     */
2844    any: function any() {
2845      var _this3 = this;
2846
2847      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
2848        args[_key4] = arguments[_key4];
2849      }
2850
2851      var _normalizeListenArgs3 = normalizeListenArgs(this, args),
2852          isTargetingSelf = _normalizeListenArgs3.isTargetingSelf,
2853          target = _normalizeListenArgs3.target,
2854          type = _normalizeListenArgs3.type,
2855          listener = _normalizeListenArgs3.listener; // Targeting this evented object.
2856
2857
2858      if (isTargetingSelf) {
2859        listen(target, 'any', type, listener); // Targeting another evented object.
2860      } else {
2861        var wrapper = function wrapper() {
2862          _this3.off(target, type, wrapper);
2863
2864          for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
2865            largs[_key5] = arguments[_key5];
2866          }
2867
2868          listener.apply(null, largs);
2869        }; // Use the same function ID as the listener so we can remove it later
2870        // it using the ID of the original listener.
2871
2872
2873        wrapper.guid = listener.guid;
2874        listen(target, 'any', type, wrapper);
2875      }
2876    },
2877
2878    /**
2879     * Removes listener(s) from event(s) on an evented object.
2880     *
2881     * @param  {string|Array|Element|Object} [targetOrType]
2882     *         If this is a string or array, it represents the event type(s).
2883     *
2884     *         Another evented object can be passed here instead, in which case
2885     *         ALL 3 arguments are _required_.
2886     *
2887     * @param  {string|Array|Function} [typeOrListener]
2888     *         If the first argument was a string or array, this may be the
2889     *         listener function. Otherwise, this is a string or array of event
2890     *         type(s).
2891     *
2892     * @param  {Function} [listener]
2893     *         If the first argument was another evented object, this will be
2894     *         the listener function; otherwise, _all_ listeners bound to the
2895     *         event type(s) will be removed.
2896     */
2897    off: function off$1(targetOrType, typeOrListener, listener) {
2898      // Targeting this evented object.
2899      if (!targetOrType || isValidEventType(targetOrType)) {
2900        off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
2901      } else {
2902        var target = targetOrType;
2903        var type = typeOrListener; // Fail fast and in a meaningful way!
2904
2905        validateTarget(target);
2906        validateEventType(type);
2907        validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
2908
2909        listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
2910        // the same guid as the event listener in on().
2911
2912        this.off('dispose', listener);
2913
2914        if (target.nodeName) {
2915          off(target, type, listener);
2916          off(target, 'dispose', listener);
2917        } else if (isEvented(target)) {
2918          target.off(type, listener);
2919          target.off('dispose', listener);
2920        }
2921      }
2922    },
2923
2924    /**
2925     * Fire an event on this evented object, causing its listeners to be called.
2926     *
2927     * @param   {string|Object} event
2928     *          An event type or an object with a type property.
2929     *
2930     * @param   {Object} [hash]
2931     *          An additional object to pass along to listeners.
2932     *
2933     * @return {boolean}
2934     *          Whether or not the default behavior was prevented.
2935     */
2936    trigger: function trigger$1(event, hash) {
2937      return trigger(this.eventBusEl_, event, hash);
2938    }
2939  };
2940  /**
2941   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
2942   *
2943   * @param  {Object} target
2944   *         The object to which to add event methods.
2945   *
2946   * @param  {Object} [options={}]
2947   *         Options for customizing the mixin behavior.
2948   *
2949   * @param  {string} [options.eventBusKey]
2950   *         By default, adds a `eventBusEl_` DOM element to the target object,
2951   *         which is used as an event bus. If the target object already has a
2952   *         DOM element that should be used, pass its key here.
2953   *
2954   * @return {Object}
2955   *         The target object.
2956   */
2957
2958  function evented(target, options) {
2959    if (options === void 0) {
2960      options = {};
2961    }
2962
2963    var _options = options,
2964        eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
2965
2966    if (eventBusKey) {
2967      if (!target[eventBusKey].nodeName) {
2968        throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
2969      }
2970
2971      target.eventBusEl_ = target[eventBusKey];
2972    } else {
2973      target.eventBusEl_ = createEl('span', {
2974        className: 'vjs-event-bus'
2975      });
2976    }
2977
2978    assign(target, EventedMixin);
2979
2980    if (target.eventedCallbacks) {
2981      target.eventedCallbacks.forEach(function (callback) {
2982        callback();
2983      });
2984    } // When any evented object is disposed, it removes all its listeners.
2985
2986
2987    target.on('dispose', function () {
2988      target.off();
2989      window$1.setTimeout(function () {
2990        target.eventBusEl_ = null;
2991      }, 0);
2992    });
2993    return target;
2994  }
2995
2996  /**
2997   * @file mixins/stateful.js
2998   * @module stateful
2999   */
3000  /**
3001   * Contains methods that provide statefulness to an object which is passed
3002   * to {@link module:stateful}.
3003   *
3004   * @mixin StatefulMixin
3005   */
3006
3007  var StatefulMixin = {
3008    /**
3009     * A hash containing arbitrary keys and values representing the state of
3010     * the object.
3011     *
3012     * @type {Object}
3013     */
3014    state: {},
3015
3016    /**
3017     * Set the state of an object by mutating its
3018     * {@link module:stateful~StatefulMixin.state|state} object in place.
3019     *
3020     * @fires   module:stateful~StatefulMixin#statechanged
3021     * @param   {Object|Function} stateUpdates
3022     *          A new set of properties to shallow-merge into the plugin state.
3023     *          Can be a plain object or a function returning a plain object.
3024     *
3025     * @return {Object|undefined}
vendor: 78,259 bytes, lines 3026-5541
3026     *          An object containing changes that occurred. If no changes
3027     *          occurred, returns `undefined`.
3028     */
3029    setState: function setState(stateUpdates) {
3030      var _this = this;
3031
3032      // Support providing the `stateUpdates` state as a function.
3033      if (typeof stateUpdates === 'function') {
3034        stateUpdates = stateUpdates();
3035      }
3036
3037      var changes;
3038      each(stateUpdates, function (value, key) {
3039        // Record the change if the value is different from what's in the
3040        // current state.
3041        if (_this.state[key] !== value) {
3042          changes = changes || {};
3043          changes[key] = {
3044            from: _this.state[key],
3045            to: value
3046          };
3047        }
3048
3049        _this.state[key] = value;
3050      }); // Only trigger "statechange" if there were changes AND we have a trigger
3051      // function. This allows us to not require that the target object be an
3052      // evented object.
3053
3054      if (changes && isEvented(this)) {
3055        /**
3056         * An event triggered on an object that is both
3057         * {@link module:stateful|stateful} and {@link module:evented|evented}
3058         * indicating that its state has changed.
3059         *
3060         * @event    module:stateful~StatefulMixin#statechanged
3061         * @type     {Object}
3062         * @property {Object} changes
3063         *           A hash containing the properties that were changed and
3064         *           the values they were changed `from` and `to`.
3065         */
3066        this.trigger({
3067          changes: changes,
3068          type: 'statechanged'
3069        });
3070      }
3071
3072      return changes;
3073    }
3074  };
3075  /**
3076   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3077   * object.
3078   *
3079   * If the target object is {@link module:evented|evented} and has a
3080   * `handleStateChanged` method, that method will be automatically bound to the
3081   * `statechanged` event on itself.
3082   *
3083   * @param   {Object} target
3084   *          The object to be made stateful.
3085   *
3086   * @param   {Object} [defaultState]
3087   *          A default set of properties to populate the newly-stateful object's
3088   *          `state` property.
3089   *
3090   * @return {Object}
3091   *          Returns the `target`.
3092   */
3093
3094  function stateful(target, defaultState) {
3095    assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
3096    // added in that step.
3097
3098    target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
3099
3100    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3101      target.on('statechanged', target.handleStateChanged);
3102    }
3103
3104    return target;
3105  }
3106
3107  /**
3108   * @file string-cases.js
3109   * @module to-lower-case
3110   */
3111
3112  /**
3113   * Lowercase the first letter of a string.
3114   *
3115   * @param {string} string
3116   *        String to be lowercased
3117   *
3118   * @return {string}
3119   *         The string with a lowercased first letter
3120   */
3121  var toLowerCase = function toLowerCase(string) {
3122    if (typeof string !== 'string') {
3123      return string;
3124    }
3125
3126    return string.replace(/./, function (w) {
3127      return w.toLowerCase();
3128    });
3129  };
3130  /**
3131   * Uppercase the first letter of a string.
3132   *
3133   * @param {string} string
3134   *        String to be uppercased
3135   *
3136   * @return {string}
3137   *         The string with an uppercased first letter
3138   */
3139
3140  var toTitleCase = function toTitleCase(string) {
3141    if (typeof string !== 'string') {
3142      return string;
3143    }
3144
3145    return string.replace(/./, function (w) {
3146      return w.toUpperCase();
3147    });
3148  };
3149  /**
3150   * Compares the TitleCase versions of the two strings for equality.
3151   *
3152   * @param {string} str1
3153   *        The first string to compare
3154   *
3155   * @param {string} str2
3156   *        The second string to compare
3157   *
3158   * @return {boolean}
3159   *         Whether the TitleCase versions of the strings are equal
3160   */
3161
3162  var titleCaseEquals = function titleCaseEquals(str1, str2) {
3163    return toTitleCase(str1) === toTitleCase(str2);
3164  };
3165
3166  /**
3167   * @file merge-options.js
3168   * @module merge-options
3169   */
3170  /**
3171   * Merge two objects recursively.
3172   *
3173   * Performs a deep merge like
3174   * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
3175   * plain objects (not arrays, elements, or anything else).
3176   *
3177   * Non-plain object values will be copied directly from the right-most
3178   * argument.
3179   *
3180   * @static
3181   * @param   {Object[]} sources
3182   *          One or more objects to merge into a new object.
3183   *
3184   * @return {Object}
3185   *          A new object that is the merged result of all sources.
3186   */
3187
3188  function mergeOptions() {
3189    var result = {};
3190
3191    for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
3192      sources[_key] = arguments[_key];
3193    }
3194
3195    sources.forEach(function (source) {
3196      if (!source) {
3197        return;
3198      }
3199
3200      each(source, function (value, key) {
3201        if (!isPlain(value)) {
3202          result[key] = value;
3203          return;
3204        }
3205
3206        if (!isPlain(result[key])) {
3207          result[key] = {};
3208        }
3209
3210        result[key] = mergeOptions(result[key], value);
3211      });
3212    });
3213    return result;
3214  }
3215
3216  var MapSham = /*#__PURE__*/function () {
3217    function MapSham() {
3218      this.map_ = {};
3219    }
3220
3221    var _proto = MapSham.prototype;
3222
3223    _proto.has = function has(key) {
3224      return key in this.map_;
3225    };
3226
3227    _proto["delete"] = function _delete(key) {
3228      var has = this.has(key);
3229      delete this.map_[key];
3230      return has;
3231    };
3232
3233    _proto.set = function set(key, value) {
3234      this.set_[key] = value;
3235      return this;
3236    };
3237
3238    _proto.forEach = function forEach(callback, thisArg) {
3239      for (var key in this.map_) {
3240        callback.call(thisArg, this.map_[key], key, this);
3241      }
3242    };
3243
3244    return MapSham;
3245  }();
3246
3247  var Map$1 = window$1.Map ? window$1.Map : MapSham;
3248
3249  var SetSham = /*#__PURE__*/function () {
3250    function SetSham() {
3251      this.set_ = {};
3252    }
3253
3254    var _proto = SetSham.prototype;
3255
3256    _proto.has = function has(key) {
3257      return key in this.set_;
3258    };
3259
3260    _proto["delete"] = function _delete(key) {
3261      var has = this.has(key);
3262      delete this.set_[key];
3263      return has;
3264    };
3265
3266    _proto.add = function add(key) {
3267      this.set_[key] = 1;
3268      return this;
3269    };
3270
3271    _proto.forEach = function forEach(callback, thisArg) {
3272      for (var key in this.set_) {
3273        callback.call(thisArg, key, key, this);
3274      }
3275    };
3276
3277    return SetSham;
3278  }();
3279
3280  var Set = window$1.Set ? window$1.Set : SetSham;
3281
3282  /**
3283   * Player Component - Base class for all UI objects
3284   *
3285   * @file component.js
3286   */
3287  /**
3288   * Base class for all UI Components.
3289   * Components are UI objects which represent both a javascript object and an element
3290   * in the DOM. They can be children of other components, and can have
3291   * children themselves.
3292   *
3293   * Components can also use methods from {@link EventTarget}
3294   */
3295
3296  var Component = /*#__PURE__*/function () {
3297    /**
3298     * A callback that is called when a component is ready. Does not have any
3299     * paramters and any callback value will be ignored.
3300     *
3301     * @callback Component~ReadyCallback
3302     * @this Component
3303     */
3304
3305    /**
3306     * Creates an instance of this class.
3307     *
3308     * @param {Player} player
3309     *        The `Player` that this class should be attached to.
3310     *
3311     * @param {Object} [options]
3312     *        The key/value store of player options.
3313     *
3314     * @param {Object[]} [options.children]
3315     *        An array of children objects to intialize this component with. Children objects have
3316     *        a name property that will be used if more than one component of the same type needs to be
3317     *        added.
3318     *
3319     * @param {Component~ReadyCallback} [ready]
3320     *        Function that gets called when the `Component` is ready.
3321     */
3322    function Component(player, options, ready) {
3323      // The component might be the player itself and we can't pass `this` to super
3324      if (!player && this.play) {
3325        this.player_ = player = this; // eslint-disable-line
3326      } else {
3327        this.player_ = player;
3328      }
3329
3330      this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method
3331
3332      this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
3333
3334      this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
3335
3336      options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
3337
3338      this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
3339
3340      if (!this.id_) {
3341        // Don't require the player ID function in the case of mock players
3342        var id = player && player.id && player.id() || 'no_player';
3343        this.id_ = id + "_component_" + newGUID();
3344      }
3345
3346      this.name_ = options.name || null; // Create element if one wasn't provided in options
3347
3348      if (options.el) {
3349        this.el_ = options.el;
3350      } else if (options.createEl !== false) {
3351        this.el_ = this.createEl();
3352      } // if evented is anything except false, we want to mixin in evented
3353
3354
3355      if (options.evented !== false) {
3356        // Make this an evented object and use `el_`, if available, as its event bus
3357        evented(this, {
3358          eventBusKey: this.el_ ? 'el_' : null
3359        });
3360      }
3361
3362      stateful(this, this.constructor.defaultState);
3363      this.children_ = [];
3364      this.childIndex_ = {};
3365      this.childNameIndex_ = {};
3366      this.setTimeoutIds_ = new Set();
3367      this.setIntervalIds_ = new Set();
3368      this.rafIds_ = new Set();
3369      this.namedRafs_ = new Map$1();
3370      this.clearingTimersOnDispose_ = false; // Add any child components in options
3371
3372      if (options.initChildren !== false) {
3373        this.initChildren();
3374      }
3375
3376      this.ready(ready); // Don't want to trigger ready here or it will before init is actually
3377      // finished for all children that run this constructor
3378
3379      if (options.reportTouchActivity !== false) {
3380        this.enableTouchActivity();
3381      }
3382    }
3383    /**
3384     * Dispose of the `Component` and all child components.
3385     *
3386     * @fires Component#dispose
3387     */
3388
3389
3390    var _proto = Component.prototype;
3391
3392    _proto.dispose = function dispose() {
3393      // Bail out if the component has already been disposed.
3394      if (this.isDisposed_) {
3395        return;
3396      }
3397      /**
3398       * Triggered when a `Component` is disposed.
3399       *
3400       * @event Component#dispose
3401       * @type {EventTarget~Event}
3402       *
3403       * @property {boolean} [bubbles=false]
3404       *           set to false so that the dispose event does not
3405       *           bubble up
3406       */
3407
3408
3409      this.trigger({
3410        type: 'dispose',
3411        bubbles: false
3412      });
3413      this.isDisposed_ = true; // Dispose all children.
3414
3415      if (this.children_) {
3416        for (var i = this.children_.length - 1; i >= 0; i--) {
3417          if (this.children_[i].dispose) {
3418            this.children_[i].dispose();
3419          }
3420        }
3421      } // Delete child references
3422
3423
3424      this.children_ = null;
3425      this.childIndex_ = null;
3426      this.childNameIndex_ = null;
3427      this.parentComponent_ = null;
3428
3429      if (this.el_) {
3430        // Remove element from DOM
3431        if (this.el_.parentNode) {
3432          this.el_.parentNode.removeChild(this.el_);
3433        }
3434
3435        if (DomData.has(this.el_)) {
3436          DomData["delete"](this.el_);
3437        }
3438
3439        this.el_ = null;
3440      } // remove reference to the player after disposing of the element
3441
3442
3443      this.player_ = null;
3444    }
3445    /**
3446     * Determine whether or not this component has been disposed.
3447     *
3448     * @return {boolean}
3449     *         If the component has been disposed, will be `true`. Otherwise, `false`.
3450     */
3451    ;
3452
3453    _proto.isDisposed = function isDisposed() {
3454      return Boolean(this.isDisposed_);
3455    }
3456    /**
3457     * Return the {@link Player} that the `Component` has attached to.
3458     *
3459     * @return {Player}
3460     *         The player that this `Component` has attached to.
3461     */
3462    ;
3463
3464    _proto.player = function player() {
3465      return this.player_;
3466    }
3467    /**
3468     * Deep merge of options objects with new options.
3469     * > Note: When both `obj` and `options` contain properties whose values are objects.
3470     *         The two properties get merged using {@link module:mergeOptions}
3471     *
3472     * @param {Object} obj
3473     *        The object that contains new options.
3474     *
3475     * @return {Object}
3476     *         A new object of `this.options_` and `obj` merged together.
3477     */
3478    ;
3479
3480    _proto.options = function options(obj) {
3481      if (!obj) {
3482        return this.options_;
3483      }
3484
3485      this.options_ = mergeOptions(this.options_, obj);
3486      return this.options_;
3487    }
3488    /**
3489     * Get the `Component`s DOM element
3490     *
3491     * @return {Element}
3492     *         The DOM element for this `Component`.
3493     */
3494    ;
3495
3496    _proto.el = function el() {
3497      return this.el_;
3498    }
3499    /**
3500     * Create the `Component`s DOM element.
3501     *
3502     * @param {string} [tagName]
3503     *        Element's DOM node type. e.g. 'div'
3504     *
3505     * @param {Object} [properties]
3506     *        An object of properties that should be set.
3507     *
3508     * @param {Object} [attributes]
3509     *        An object of attributes that should be set.
3510     *
3511     * @return {Element}
3512     *         The element that gets created.
3513     */
3514    ;
3515
3516    _proto.createEl = function createEl$1(tagName, properties, attributes) {
3517      return createEl(tagName, properties, attributes);
3518    }
3519    /**
3520     * Localize a string given the string in english.
3521     *
3522     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3523     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3524     *
3525     * If a `defaultValue` is provided, it'll use that over `string`,
3526     * if a value isn't found in provided language files.
3527     * This is useful if you want to have a descriptive key for token replacement
3528     * but have a succinct localized string and not require `en.json` to be included.
3529     *
3530     * Currently, it is used for the progress bar timing.
3531     * ```js
3532     * {
3533     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3534     * }
3535     * ```
3536     * It is then used like so:
3537     * ```js
3538     * this.localize('progress bar timing: currentTime={1} duration{2}',
3539     *               [this.player_.currentTime(), this.player_.duration()],
3540     *               '{1} of {2}');
3541     * ```
3542     *
3543     * Which outputs something like: `01:23 of 24:56`.
3544     *
3545     *
3546     * @param {string} string
3547     *        The string to localize and the key to lookup in the language files.
3548     * @param {string[]} [tokens]
3549     *        If the current item has token replacements, provide the tokens here.
3550     * @param {string} [defaultValue]
3551     *        Defaults to `string`. Can be a default value to use for token replacement
3552     *        if the lookup key is needed to be separate.
3553     *
3554     * @return {string}
3555     *         The localized string or if no localization exists the english string.
3556     */
3557    ;
3558
3559    _proto.localize = function localize(string, tokens, defaultValue) {
3560      if (defaultValue === void 0) {
3561        defaultValue = string;
3562      }
3563
3564      var code = this.player_.language && this.player_.language();
3565      var languages = this.player_.languages && this.player_.languages();
3566      var language = languages && languages[code];
3567      var primaryCode = code && code.split('-')[0];
3568      var primaryLang = languages && languages[primaryCode];
3569      var localizedString = defaultValue;
3570
3571      if (language && language[string]) {
3572        localizedString = language[string];
3573      } else if (primaryLang && primaryLang[string]) {
3574        localizedString = primaryLang[string];
3575      }
3576
3577      if (tokens) {
3578        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3579          var value = tokens[index - 1];
3580          var ret = value;
3581
3582          if (typeof value === 'undefined') {
3583            ret = match;
3584          }
3585
3586          return ret;
3587        });
3588      }
3589
3590      return localizedString;
3591    }
3592    /**
3593     * Return the `Component`s DOM element. This is where children get inserted.
3594     * This will usually be the the same as the element returned in {@link Component#el}.
3595     *
3596     * @return {Element}
3597     *         The content element for this `Component`.
3598     */
3599    ;
3600
3601    _proto.contentEl = function contentEl() {
3602      return this.contentEl_ || this.el_;
3603    }
3604    /**
3605     * Get this `Component`s ID
3606     *
3607     * @return {string}
3608     *         The id of this `Component`
3609     */
3610    ;
3611
3612    _proto.id = function id() {
3613      return this.id_;
3614    }
3615    /**
3616     * Get the `Component`s name. The name gets used to reference the `Component`
3617     * and is set during registration.
3618     *
3619     * @return {string}
3620     *         The name of this `Component`.
3621     */
3622    ;
3623
3624    _proto.name = function name() {
3625      return this.name_;
3626    }
3627    /**
3628     * Get an array of all child components
3629     *
3630     * @return {Array}
3631     *         The children
3632     */
3633    ;
3634
3635    _proto.children = function children() {
3636      return this.children_;
3637    }
3638    /**
3639     * Returns the child `Component` with the given `id`.
3640     *
3641     * @param {string} id
3642     *        The id of the child `Component` to get.
3643     *
3644     * @return {Component|undefined}
3645     *         The child `Component` with the given `id` or undefined.
3646     */
3647    ;
3648
3649    _proto.getChildById = function getChildById(id) {
3650      return this.childIndex_[id];
3651    }
3652    /**
3653     * Returns the child `Component` with the given `name`.
3654     *
3655     * @param {string} name
3656     *        The name of the child `Component` to get.
3657     *
3658     * @return {Component|undefined}
3659     *         The child `Component` with the given `name` or undefined.
3660     */
3661    ;
3662
3663    _proto.getChild = function getChild(name) {
3664      if (!name) {
3665        return;
3666      }
3667
3668      return this.childNameIndex_[name];
3669    }
3670    /**
3671     * Returns the descendant `Component` following the givent
3672     * descendant `names`. For instance ['foo', 'bar', 'baz'] would
3673     * try to get 'foo' on the current component, 'bar' on the 'foo'
3674     * component and 'baz' on the 'bar' component and return undefined
3675     * if any of those don't exist.
3676     *
3677     * @param {...string[]|...string} names
3678     *        The name of the child `Component` to get.
3679     *
3680     * @return {Component|undefined}
3681     *         The descendant `Component` following the given descendant
3682     *         `names` or undefined.
3683     */
3684    ;
3685
3686    _proto.getDescendant = function getDescendant() {
3687      for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) {
3688        names[_key] = arguments[_key];
3689      }
3690
3691      // flatten array argument into the main array
3692      names = names.reduce(function (acc, n) {
3693        return acc.concat(n);
3694      }, []);
3695      var currentChild = this;
3696
3697      for (var i = 0; i < names.length; i++) {
3698        currentChild = currentChild.getChild(names[i]);
3699
3700        if (!currentChild || !currentChild.getChild) {
3701          return;
3702        }
3703      }
3704
3705      return currentChild;
3706    }
3707    /**
3708     * Add a child `Component` inside the current `Component`.
3709     *
3710     *
3711     * @param {string|Component} child
3712     *        The name or instance of a child to add.
3713     *
3714     * @param {Object} [options={}]
3715     *        The key/value store of options that will get passed to children of
3716     *        the child.
3717     *
3718     * @param {number} [index=this.children_.length]
3719     *        The index to attempt to add a child into.
3720     *
3721     * @return {Component}
3722     *         The `Component` that gets added as a child. When using a string the
3723     *         `Component` will get created by this process.
3724     */
3725    ;
3726
3727    _proto.addChild = function addChild(child, options, index) {
3728      if (options === void 0) {
3729        options = {};
3730      }
3731
3732      if (index === void 0) {
3733        index = this.children_.length;
3734      }
3735
3736      var component;
3737      var componentName; // If child is a string, create component with options
3738
3739      if (typeof child === 'string') {
3740        componentName = toTitleCase(child);
3741        var componentClassName = options.componentClass || componentName; // Set name through options
3742
3743        options.name = componentName; // Create a new object & element for this controls set
3744        // If there's no .player_, this is a player
3745
3746        var ComponentClass = Component.getComponent(componentClassName);
3747
3748        if (!ComponentClass) {
3749          throw new Error("Component " + componentClassName + " does not exist");
3750        } // data stored directly on the videojs object may be
3751        // misidentified as a component to retain
3752        // backwards-compatibility with 4.x. check to make sure the
3753        // component class can be instantiated.
3754
3755
3756        if (typeof ComponentClass !== 'function') {
3757          return null;
3758        }
3759
3760        component = new ComponentClass(this.player_ || this, options); // child is a component instance
3761      } else {
3762        component = child;
3763      }
3764
3765      if (component.parentComponent_) {
3766        component.parentComponent_.removeChild(component);
3767      }
3768
3769      this.children_.splice(index, 0, component);
3770      component.parentComponent_ = this;
3771
3772      if (typeof component.id === 'function') {
3773        this.childIndex_[component.id()] = component;
3774      } // If a name wasn't used to create the component, check if we can use the
3775      // name function of the component
3776
3777
3778      componentName = componentName || component.name && toTitleCase(component.name());
3779
3780      if (componentName) {
3781        this.childNameIndex_[componentName] = component;
3782        this.childNameIndex_[toLowerCase(componentName)] = component;
3783      } // Add the UI object's element to the container div (box)
3784      // Having an element is not required
3785
3786
3787      if (typeof component.el === 'function' && component.el()) {
3788        // If inserting before a component, insert before that component's element
3789        var refNode = null;
3790
3791        if (this.children_[index + 1]) {
3792          // Most children are components, but the video tech is an HTML element
3793          if (this.children_[index + 1].el_) {
3794            refNode = this.children_[index + 1].el_;
3795          } else if (isEl(this.children_[index + 1])) {
3796            refNode = this.children_[index + 1];
3797          }
3798        }
3799
3800        this.contentEl().insertBefore(component.el(), refNode);
3801      } // Return so it can stored on parent object if desired.
3802
3803
3804      return component;
3805    }
3806    /**
3807     * Remove a child `Component` from this `Component`s list of children. Also removes
3808     * the child `Component`s element from this `Component`s element.
3809     *
3810     * @param {Component} component
3811     *        The child `Component` to remove.
3812     */
3813    ;
3814
3815    _proto.removeChild = function removeChild(component) {
3816      if (typeof component === 'string') {
3817        component = this.getChild(component);
3818      }
3819
3820      if (!component || !this.children_) {
3821        return;
3822      }
3823
3824      var childFound = false;
3825
3826      for (var i = this.children_.length - 1; i >= 0; i--) {
3827        if (this.children_[i] === component) {
3828          childFound = true;
3829          this.children_.splice(i, 1);
3830          break;
3831        }
3832      }
3833
3834      if (!childFound) {
3835        return;
3836      }
3837
3838      component.parentComponent_ = null;
3839      this.childIndex_[component.id()] = null;
3840      this.childNameIndex_[toTitleCase(component.name())] = null;
3841      this.childNameIndex_[toLowerCase(component.name())] = null;
3842      var compEl = component.el();
3843
3844      if (compEl && compEl.parentNode === this.contentEl()) {
3845        this.contentEl().removeChild(component.el());
3846      }
3847    }
3848    /**
3849     * Add and initialize default child `Component`s based upon options.
3850     */
3851    ;
3852
3853    _proto.initChildren = function initChildren() {
3854      var _this = this;
3855
3856      var children = this.options_.children;
3857
3858      if (children) {
3859        // `this` is `parent`
3860        var parentOptions = this.options_;
3861
3862        var handleAdd = function handleAdd(child) {
3863          var name = child.name;
3864          var opts = child.opts; // Allow options for children to be set at the parent options
3865          // e.g. videojs(id, { controlBar: false });
3866          // instead of videojs(id, { children: { controlBar: false });
3867
3868          if (parentOptions[name] !== undefined) {
3869            opts = parentOptions[name];
3870          } // Allow for disabling default components
3871          // e.g. options['children']['posterImage'] = false
3872
3873
3874          if (opts === false) {
3875            return;
3876          } // Allow options to be passed as a simple boolean if no configuration
3877          // is necessary.
3878
3879
3880          if (opts === true) {
3881            opts = {};
3882          } // We also want to pass the original player options
3883          // to each component as well so they don't need to
3884          // reach back into the player for options later.
3885
3886
3887          opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
3888          // Add a direct reference to the child by name on the parent instance.
3889          // If two of the same component are used, different names should be supplied
3890          // for each
3891
3892          var newChild = _this.addChild(name, opts);
3893
3894          if (newChild) {
3895            _this[name] = newChild;
3896          }
3897        }; // Allow for an array of children details to passed in the options
3898
3899
3900        var workingChildren;
3901        var Tech = Component.getComponent('Tech');
3902
3903        if (Array.isArray(children)) {
3904          workingChildren = children;
3905        } else {
3906          workingChildren = Object.keys(children);
3907        }
3908
3909        workingChildren // children that are in this.options_ but also in workingChildren  would
3910        // give us extra children we do not want. So, we want to filter them out.
3911        .concat(Object.keys(this.options_).filter(function (child) {
3912          return !workingChildren.some(function (wchild) {
3913            if (typeof wchild === 'string') {
3914              return child === wchild;
3915            }
3916
3917            return child === wchild.name;
3918          });
3919        })).map(function (child) {
3920          var name;
3921          var opts;
3922
3923          if (typeof child === 'string') {
3924            name = child;
3925            opts = children[name] || _this.options_[name] || {};
3926          } else {
3927            name = child.name;
3928            opts = child;
3929          }
3930
3931          return {
3932            name: name,
3933            opts: opts
3934          };
3935        }).filter(function (child) {
3936          // we have to make sure that child.name isn't in the techOrder since
3937          // techs are registerd as Components but can't aren't compatible
3938          // See https://github.com/videojs/video.js/issues/2772
3939          var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
3940          return c && !Tech.isTech(c);
3941        }).forEach(handleAdd);
3942      }
3943    }
3944    /**
3945     * Builds the default DOM class name. Should be overriden by sub-components.
3946     *
3947     * @return {string}
3948     *         The DOM class name for this object.
3949     *
3950     * @abstract
3951     */
3952    ;
3953
3954    _proto.buildCSSClass = function buildCSSClass() {
3955      // Child classes can include a function that does:
3956      // return 'CLASS NAME' + this._super();
3957      return '';
3958    }
3959    /**
3960     * Bind a listener to the component's ready state.
3961     * Different from event listeners in that if the ready event has already happened
3962     * it will trigger the function immediately.
3963     *
3964     * @return {Component}
3965     *         Returns itself; method can be chained.
3966     */
3967    ;
3968
3969    _proto.ready = function ready(fn, sync) {
3970      if (sync === void 0) {
3971        sync = false;
3972      }
3973
3974      if (!fn) {
3975        return;
3976      }
3977
3978      if (!this.isReady_) {
3979        this.readyQueue_ = this.readyQueue_ || [];
3980        this.readyQueue_.push(fn);
3981        return;
3982      }
3983
3984      if (sync) {
3985        fn.call(this);
3986      } else {
3987        // Call the function asynchronously by default for consistency
3988        this.setTimeout(fn, 1);
3989      }
3990    }
3991    /**
3992     * Trigger all the ready listeners for this `Component`.
3993     *
3994     * @fires Component#ready
3995     */
3996    ;
3997
3998    _proto.triggerReady = function triggerReady() {
3999      this.isReady_ = true; // Ensure ready is triggered asynchronously
4000
4001      this.setTimeout(function () {
4002        var readyQueue = this.readyQueue_; // Reset Ready Queue
4003
4004        this.readyQueue_ = [];
4005
4006        if (readyQueue && readyQueue.length > 0) {
4007          readyQueue.forEach(function (fn) {
4008            fn.call(this);
4009          }, this);
4010        } // Allow for using event listeners also
4011
4012        /**
4013         * Triggered when a `Component` is ready.
4014         *
4015         * @event Component#ready
4016         * @type {EventTarget~Event}
4017         */
4018
4019
4020        this.trigger('ready');
4021      }, 1);
4022    }
4023    /**
4024     * Find a single DOM element matching a `selector`. This can be within the `Component`s
4025     * `contentEl()` or another custom context.
4026     *
4027     * @param {string} selector
4028     *        A valid CSS selector, which will be passed to `querySelector`.
4029     *
4030     * @param {Element|string} [context=this.contentEl()]
4031     *        A DOM element within which to query. Can also be a selector string in
4032     *        which case the first matching element will get used as context. If
4033     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4034     *        nothing it falls back to `document`.
4035     *
4036     * @return {Element|null}
4037     *         the dom element that was found, or null
4038     *
4039     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4040     */
4041    ;
4042
4043    _proto.$ = function $$1(selector, context) {
4044      return $(selector, context || this.contentEl());
4045    }
4046    /**
4047     * Finds all DOM element matching a `selector`. This can be within the `Component`s
4048     * `contentEl()` or another custom context.
4049     *
4050     * @param {string} selector
4051     *        A valid CSS selector, which will be passed to `querySelectorAll`.
4052     *
4053     * @param {Element|string} [context=this.contentEl()]
4054     *        A DOM element within which to query. Can also be a selector string in
4055     *        which case the first matching element will get used as context. If
4056     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4057     *        nothing it falls back to `document`.
4058     *
4059     * @return {NodeList}
4060     *         a list of dom elements that were found
4061     *
4062     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4063     */
4064    ;
4065
4066    _proto.$$ = function $$$1(selector, context) {
4067      return $$(selector, context || this.contentEl());
4068    }
4069    /**
4070     * Check if a component's element has a CSS class name.
4071     *
4072     * @param {string} classToCheck
4073     *        CSS class name to check.
4074     *
4075     * @return {boolean}
4076     *         - True if the `Component` has the class.
4077     *         - False if the `Component` does not have the class`
4078     */
4079    ;
4080
4081    _proto.hasClass = function hasClass$1(classToCheck) {
4082      return hasClass(this.el_, classToCheck);
4083    }
4084    /**
4085     * Add a CSS class name to the `Component`s element.
4086     *
4087     * @param {string} classToAdd
4088     *        CSS class name to add
4089     */
4090    ;
4091
4092    _proto.addClass = function addClass$1(classToAdd) {
4093      addClass(this.el_, classToAdd);
4094    }
4095    /**
4096     * Remove a CSS class name from the `Component`s element.
4097     *
4098     * @param {string} classToRemove
4099     *        CSS class name to remove
4100     */
4101    ;
4102
4103    _proto.removeClass = function removeClass$1(classToRemove) {
4104      removeClass(this.el_, classToRemove);
4105    }
4106    /**
4107     * Add or remove a CSS class name from the component's element.
4108     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4109     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4110     *
4111     * @param  {string} classToToggle
4112     *         The class to add or remove based on (@link Component#hasClass}
4113     *
4114     * @param  {boolean|Dom~predicate} [predicate]
4115     *         An {@link Dom~predicate} function or a boolean
4116     */
4117    ;
4118
4119    _proto.toggleClass = function toggleClass$1(classToToggle, predicate) {
4120      toggleClass(this.el_, classToToggle, predicate);
4121    }
4122    /**
4123     * Show the `Component`s element if it is hidden by removing the
4124     * 'vjs-hidden' class name from it.
4125     */
4126    ;
4127
4128    _proto.show = function show() {
4129      this.removeClass('vjs-hidden');
4130    }
4131    /**
4132     * Hide the `Component`s element if it is currently showing by adding the
4133     * 'vjs-hidden` class name to it.
4134     */
4135    ;
4136
4137    _proto.hide = function hide() {
4138      this.addClass('vjs-hidden');
4139    }
4140    /**
4141     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4142     * class name to it. Used during fadeIn/fadeOut.
4143     *
4144     * @private
4145     */
4146    ;
4147
4148    _proto.lockShowing = function lockShowing() {
4149      this.addClass('vjs-lock-showing');
4150    }
4151    /**
4152     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4153     * class name from it. Used during fadeIn/fadeOut.
4154     *
4155     * @private
4156     */
4157    ;
4158
4159    _proto.unlockShowing = function unlockShowing() {
4160      this.removeClass('vjs-lock-showing');
4161    }
4162    /**
4163     * Get the value of an attribute on the `Component`s element.
4164     *
4165     * @param {string} attribute
4166     *        Name of the attribute to get the value from.
4167     *
4168     * @return {string|null}
4169     *         - The value of the attribute that was asked for.
4170     *         - Can be an empty string on some browsers if the attribute does not exist
4171     *           or has no value
4172     *         - Most browsers will return null if the attibute does not exist or has
4173     *           no value.
4174     *
4175     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4176     */
4177    ;
4178
4179    _proto.getAttribute = function getAttribute$1(attribute) {
4180      return getAttribute(this.el_, attribute);
4181    }
4182    /**
4183     * Set the value of an attribute on the `Component`'s element
4184     *
4185     * @param {string} attribute
4186     *        Name of the attribute to set.
4187     *
4188     * @param {string} value
4189     *        Value to set the attribute to.
4190     *
4191     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4192     */
4193    ;
4194
4195    _proto.setAttribute = function setAttribute$1(attribute, value) {
4196      setAttribute(this.el_, attribute, value);
4197    }
4198    /**
4199     * Remove an attribute from the `Component`s element.
4200     *
4201     * @param {string} attribute
4202     *        Name of the attribute to remove.
4203     *
4204     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4205     */
4206    ;
4207
4208    _proto.removeAttribute = function removeAttribute$1(attribute) {
4209      removeAttribute(this.el_, attribute);
4210    }
4211    /**
4212     * Get or set the width of the component based upon the CSS styles.
4213     * See {@link Component#dimension} for more detailed information.
4214     *
4215     * @param {number|string} [num]
4216     *        The width that you want to set postfixed with '%', 'px' or nothing.
4217     *
4218     * @param {boolean} [skipListeners]
4219     *        Skip the componentresize event trigger
4220     *
4221     * @return {number|string}
4222     *         The width when getting, zero if there is no width. Can be a string
4223     *           postpixed with '%' or 'px'.
4224     */
4225    ;
4226
4227    _proto.width = function width(num, skipListeners) {
4228      return this.dimension('width', num, skipListeners);
4229    }
4230    /**
4231     * Get or set the height of the component based upon the CSS styles.
4232     * See {@link Component#dimension} for more detailed information.
4233     *
4234     * @param {number|string} [num]
4235     *        The height that you want to set postfixed with '%', 'px' or nothing.
4236     *
4237     * @param {boolean} [skipListeners]
4238     *        Skip the componentresize event trigger
4239     *
4240     * @return {number|string}
4241     *         The width when getting, zero if there is no width. Can be a string
4242     *         postpixed with '%' or 'px'.
4243     */
4244    ;
4245
4246    _proto.height = function height(num, skipListeners) {
4247      return this.dimension('height', num, skipListeners);
4248    }
4249    /**
4250     * Set both the width and height of the `Component` element at the same time.
4251     *
4252     * @param  {number|string} width
4253     *         Width to set the `Component`s element to.
4254     *
4255     * @param  {number|string} height
4256     *         Height to set the `Component`s element to.
4257     */
4258    ;
4259
4260    _proto.dimensions = function dimensions(width, height) {
4261      // Skip componentresize listeners on width for optimization
4262      this.width(width, true);
4263      this.height(height);
4264    }
4265    /**
4266     * Get or set width or height of the `Component` element. This is the shared code
4267     * for the {@link Component#width} and {@link Component#height}.
4268     *
4269     * Things to know:
4270     * - If the width or height in an number this will return the number postfixed with 'px'.
4271     * - If the width/height is a percent this will return the percent postfixed with '%'
4272     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4273     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4274     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
4275     *   for more information
4276     * - If you want the computed style of the component, use {@link Component#currentWidth}
4277     *   and {@link {Component#currentHeight}
4278     *
4279     * @fires Component#componentresize
4280     *
4281     * @param {string} widthOrHeight
4282     8        'width' or 'height'
4283     *
4284     * @param  {number|string} [num]
4285     8         New dimension
4286     *
4287     * @param  {boolean} [skipListeners]
4288     *         Skip componentresize event trigger
4289     *
4290     * @return {number}
4291     *         The dimension when getting or 0 if unset
4292     */
4293    ;
4294
4295    _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
4296      if (num !== undefined) {
4297        // Set to zero if null or literally NaN (NaN !== NaN)
4298        if (num === null || num !== num) {
4299          num = 0;
4300        } // Check if using css width/height (% or px) and adjust
4301
4302
4303        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4304          this.el_.style[widthOrHeight] = num;
4305        } else if (num === 'auto') {
4306          this.el_.style[widthOrHeight] = '';
4307        } else {
4308          this.el_.style[widthOrHeight] = num + 'px';
4309        } // skipListeners allows us to avoid triggering the resize event when setting both width and height
4310
4311
4312        if (!skipListeners) {
4313          /**
4314           * Triggered when a component is resized.
4315           *
4316           * @event Component#componentresize
4317           * @type {EventTarget~Event}
4318           */
4319          this.trigger('componentresize');
4320        }
4321
4322        return;
4323      } // Not setting a value, so getting it
4324      // Make sure element exists
4325
4326
4327      if (!this.el_) {
4328        return 0;
4329      } // Get dimension value from style
4330
4331
4332      var val = this.el_.style[widthOrHeight];
4333      var pxIndex = val.indexOf('px');
4334
4335      if (pxIndex !== -1) {
4336        // Return the pixel value with no 'px'
4337        return parseInt(val.slice(0, pxIndex), 10);
4338      } // No px so using % or no style was set, so falling back to offsetWidth/height
4339      // If component has display:none, offset will return 0
4340      // TODO: handle display:none and no dimension style using px
4341
4342
4343      return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
4344    }
4345    /**
4346     * Get the computed width or the height of the component's element.
4347     *
4348     * Uses `window.getComputedStyle`.
4349     *
4350     * @param {string} widthOrHeight
4351     *        A string containing 'width' or 'height'. Whichever one you want to get.
4352     *
4353     * @return {number}
4354     *         The dimension that gets asked for or 0 if nothing was set
4355     *         for that dimension.
4356     */
4357    ;
4358
4359    _proto.currentDimension = function currentDimension(widthOrHeight) {
4360      var computedWidthOrHeight = 0;
4361
4362      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4363        throw new Error('currentDimension only accepts width or height value');
4364      }
4365
4366      computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer
4367
4368      computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
4369      // and we want to check the offset values.
4370      // This code also runs wherever getComputedStyle doesn't exist.
4371
4372      if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4373        var rule = "offset" + toTitleCase(widthOrHeight);
4374        computedWidthOrHeight = this.el_[rule];
4375      }
4376
4377      return computedWidthOrHeight;
4378    }
4379    /**
4380     * An object that contains width and height values of the `Component`s
4381     * computed style. Uses `window.getComputedStyle`.
4382     *
4383     * @typedef {Object} Component~DimensionObject
4384     *
4385     * @property {number} width
4386     *           The width of the `Component`s computed style.
4387     *
4388     * @property {number} height
4389     *           The height of the `Component`s computed style.
4390     */
4391
4392    /**
4393     * Get an object that contains computed width and height values of the
4394     * component's element.
4395     *
4396     * Uses `window.getComputedStyle`.
4397     *
4398     * @return {Component~DimensionObject}
4399     *         The computed dimensions of the component's element.
4400     */
4401    ;
4402
4403    _proto.currentDimensions = function currentDimensions() {
4404      return {
4405        width: this.currentDimension('width'),
4406        height: this.currentDimension('height')
4407      };
4408    }
4409    /**
4410     * Get the computed width of the component's element.
4411     *
4412     * Uses `window.getComputedStyle`.
4413     *
4414     * @return {number}
4415     *         The computed width of the component's element.
4416     */
4417    ;
4418
4419    _proto.currentWidth = function currentWidth() {
4420      return this.currentDimension('width');
4421    }
4422    /**
4423     * Get the computed height of the component's element.
4424     *
4425     * Uses `window.getComputedStyle`.
4426     *
4427     * @return {number}
4428     *         The computed height of the component's element.
4429     */
4430    ;
4431
4432    _proto.currentHeight = function currentHeight() {
4433      return this.currentDimension('height');
4434    }
4435    /**
4436     * Set the focus to this component
4437     */
4438    ;
4439
4440    _proto.focus = function focus() {
4441      this.el_.focus();
4442    }
4443    /**
4444     * Remove the focus from this component
4445     */
4446    ;
4447
4448    _proto.blur = function blur() {
4449      this.el_.blur();
4450    }
4451    /**
4452     * When this Component receives a `keydown` event which it does not process,
4453     *  it passes the event to the Player for handling.
4454     *
4455     * @param {EventTarget~Event} event
4456     *        The `keydown` event that caused this function to be called.
4457     */
4458    ;
4459
4460    _proto.handleKeyDown = function handleKeyDown(event) {
4461      if (this.player_) {
4462        // We only stop propagation here because we want unhandled events to fall
4463        // back to the browser.
4464        event.stopPropagation();
4465        this.player_.handleKeyDown(event);
4466      }
4467    }
4468    /**
4469     * Many components used to have a `handleKeyPress` method, which was poorly
4470     * named because it listened to a `keydown` event. This method name now
4471     * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4472     * will not see their method calls stop working.
4473     *
4474     * @param {EventTarget~Event} event
4475     *        The event that caused this function to be called.
4476     */
4477    ;
4478
4479    _proto.handleKeyPress = function handleKeyPress(event) {
4480      this.handleKeyDown(event);
4481    }
4482    /**
4483     * Emit a 'tap' events when touch event support gets detected. This gets used to
4484     * support toggling the controls through a tap on the video. They get enabled
4485     * because every sub-component would have extra overhead otherwise.
4486     *
4487     * @private
4488     * @fires Component#tap
4489     * @listens Component#touchstart
4490     * @listens Component#touchmove
4491     * @listens Component#touchleave
4492     * @listens Component#touchcancel
4493     * @listens Component#touchend
4494      */
4495    ;
4496
4497    _proto.emitTapEvents = function emitTapEvents() {
4498      // Track the start time so we can determine how long the touch lasted
4499      var touchStart = 0;
4500      var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
4501      // Other popular libs use anywhere from 2 (hammer.js) to 15,
4502      // so 10 seems like a nice, round number.
4503
4504      var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
4505
4506      var touchTimeThreshold = 200;
4507      var couldBeTap;
4508      this.on('touchstart', function (event) {
4509        // If more than one finger, don't consider treating this as a click
4510        if (event.touches.length === 1) {
4511          // Copy pageX/pageY from the object
4512          firstTouch = {
4513            pageX: event.touches[0].pageX,
4514            pageY: event.touches[0].pageY
4515          }; // Record start time so we can detect a tap vs. "touch and hold"
4516
4517          touchStart = window$1.performance.now(); // Reset couldBeTap tracking
4518
4519          couldBeTap = true;
4520        }
4521      });
4522      this.on('touchmove', function (event) {
4523        // If more than one finger, don't consider treating this as a click
4524        if (event.touches.length > 1) {
4525          couldBeTap = false;
4526        } else if (firstTouch) {
4527          // Some devices will throw touchmoves for all but the slightest of taps.
4528          // So, if we moved only a small distance, this could still be a tap
4529          var xdiff = event.touches[0].pageX - firstTouch.pageX;
4530          var ydiff = event.touches[0].pageY - firstTouch.pageY;
4531          var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4532
4533          if (touchDistance > tapMovementThreshold) {
4534            couldBeTap = false;
4535          }
4536        }
4537      });
4538
4539      var noTap = function noTap() {
4540        couldBeTap = false;
4541      }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4542
4543
4544      this.on('touchleave', noTap);
4545      this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
4546      // event
4547
4548      this.on('touchend', function (event) {
4549        firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
4550
4551        if (couldBeTap === true) {
4552          // Measure how long the touch lasted
4553          var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
4554
4555          if (touchTime < touchTimeThreshold) {
4556            // Don't let browser turn this into a click
4557            event.preventDefault();
4558            /**
4559             * Triggered when a `Component` is tapped.
4560             *
4561             * @event Component#tap
4562             * @type {EventTarget~Event}
4563             */
4564
4565            this.trigger('tap'); // It may be good to copy the touchend event object and change the
4566            // type to tap, if the other event properties aren't exact after
4567            // Events.fixEvent runs (e.g. event.target)
4568          }
4569        }
4570      });
4571    }
4572    /**
4573     * This function reports user activity whenever touch events happen. This can get
4574     * turned off by any sub-components that wants touch events to act another way.
4575     *
4576     * Report user touch activity when touch events occur. User activity gets used to
4577     * determine when controls should show/hide. It is simple when it comes to mouse
4578     * events, because any mouse event should show the controls. So we capture mouse
4579     * events that bubble up to the player and report activity when that happens.
4580     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4581     * controls. So touch events can't help us at the player level either.
4582     *
4583     * User activity gets checked asynchronously. So what could happen is a tap event
4584     * on the video turns the controls off. Then the `touchend` event bubbles up to
4585     * the player. Which, if it reported user activity, would turn the controls right
4586     * back on. We also don't want to completely block touch events from bubbling up.
4587     * Furthermore a `touchmove` event and anything other than a tap, should not turn
4588     * controls back on.
4589     *
4590     * @listens Component#touchstart
4591     * @listens Component#touchmove
4592     * @listens Component#touchend
4593     * @listens Component#touchcancel
4594     */
4595    ;
4596
4597    _proto.enableTouchActivity = function enableTouchActivity() {
4598      // Don't continue if the root player doesn't support reporting user activity
4599      if (!this.player() || !this.player().reportUserActivity) {
4600        return;
4601      } // listener for reporting that the user is active
4602
4603
4604      var report = bind(this.player(), this.player().reportUserActivity);
4605      var touchHolding;
4606      this.on('touchstart', function () {
4607        report(); // For as long as the they are touching the device or have their mouse down,
4608        // we consider them active even if they're not moving their finger or mouse.
4609        // So we want to continue to update that they are active
4610
4611        this.clearInterval(touchHolding); // report at the same interval as activityCheck
4612
4613        touchHolding = this.setInterval(report, 250);
4614      });
4615
4616      var touchEnd = function touchEnd(event) {
4617        report(); // stop the interval that maintains activity if the touch is holding
4618
4619        this.clearInterval(touchHolding);
4620      };
4621
4622      this.on('touchmove', report);
4623      this.on('touchend', touchEnd);
4624      this.on('touchcancel', touchEnd);
4625    }
4626    /**
4627     * A callback that has no parameters and is bound into `Component`s context.
4628     *
4629     * @callback Component~GenericCallback
4630     * @this Component
4631     */
4632
4633    /**
4634     * Creates a function that runs after an `x` millisecond timeout. This function is a
4635     * wrapper around `window.setTimeout`. There are a few reasons to use this one
4636     * instead though:
4637     * 1. It gets cleared via  {@link Component#clearTimeout} when
4638     *    {@link Component#dispose} gets called.
4639     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
4640     *
4641     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
4642     *         will cause its dispose listener not to get cleaned up! Please use
4643     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
4644     *
4645     * @param {Component~GenericCallback} fn
4646     *        The function that will be run after `timeout`.
4647     *
4648     * @param {number} timeout
4649     *        Timeout in milliseconds to delay before executing the specified function.
4650     *
4651     * @return {number}
4652     *         Returns a timeout ID that gets used to identify the timeout. It can also
4653     *         get used in {@link Component#clearTimeout} to clear the timeout that
4654     *         was set.
4655     *
4656     * @listens Component#dispose
4657     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
4658     */
4659    ;
4660
4661    _proto.setTimeout = function setTimeout(fn, timeout) {
4662      var _this2 = this;
4663
4664      // declare as variables so they are properly available in timeout function
4665      // eslint-disable-next-line
4666      var timeoutId;
4667      fn = bind(this, fn);
4668      this.clearTimersOnDispose_();
4669      timeoutId = window$1.setTimeout(function () {
4670        if (_this2.setTimeoutIds_.has(timeoutId)) {
4671          _this2.setTimeoutIds_["delete"](timeoutId);
4672        }
4673
4674        fn();
4675      }, timeout);
4676      this.setTimeoutIds_.add(timeoutId);
4677      return timeoutId;
4678    }
4679    /**
4680     * Clears a timeout that gets created via `window.setTimeout` or
4681     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4682     * use this function instead of `window.clearTimout`. If you don't your dispose
4683     * listener will not get cleaned up until {@link Component#dispose}!
4684     *
4685     * @param {number} timeoutId
4686     *        The id of the timeout to clear. The return value of
4687     *        {@link Component#setTimeout} or `window.setTimeout`.
4688     *
4689     * @return {number}
4690     *         Returns the timeout id that was cleared.
4691     *
4692     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4693     */
4694    ;
4695
4696    _proto.clearTimeout = function clearTimeout(timeoutId) {
4697      if (this.setTimeoutIds_.has(timeoutId)) {
4698        this.setTimeoutIds_["delete"](timeoutId);
4699        window$1.clearTimeout(timeoutId);
4700      }
4701
4702      return timeoutId;
4703    }
4704    /**
4705     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4706     * around `window.setInterval`. There are a few reasons to use this one instead though.
4707     * 1. It gets cleared via  {@link Component#clearInterval} when
4708     *    {@link Component#dispose} gets called.
4709     * 2. The function callback will be a {@link Component~GenericCallback}
4710     *
4711     * @param {Component~GenericCallback} fn
4712     *        The function to run every `x` seconds.
4713     *
4714     * @param {number} interval
4715     *        Execute the specified function every `x` milliseconds.
4716     *
4717     * @return {number}
4718     *         Returns an id that can be used to identify the interval. It can also be be used in
4719     *         {@link Component#clearInterval} to clear the interval.
4720     *
4721     * @listens Component#dispose
4722     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4723     */
4724    ;
4725
4726    _proto.setInterval = function setInterval(fn, interval) {
4727      fn = bind(this, fn);
4728      this.clearTimersOnDispose_();
4729      var intervalId = window$1.setInterval(fn, interval);
4730      this.setIntervalIds_.add(intervalId);
4731      return intervalId;
4732    }
4733    /**
4734     * Clears an interval that gets created via `window.setInterval` or
4735     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4736     * use this function instead of `window.clearInterval`. If you don't your dispose
4737     * listener will not get cleaned up until {@link Component#dispose}!
4738     *
4739     * @param {number} intervalId
4740     *        The id of the interval to clear. The return value of
4741     *        {@link Component#setInterval} or `window.setInterval`.
4742     *
4743     * @return {number}
4744     *         Returns the interval id that was cleared.
4745     *
4746     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4747     */
4748    ;
4749
4750    _proto.clearInterval = function clearInterval(intervalId) {
4751      if (this.setIntervalIds_.has(intervalId)) {
4752        this.setIntervalIds_["delete"](intervalId);
4753        window$1.clearInterval(intervalId);
4754      }
4755
4756      return intervalId;
4757    }
4758    /**
4759     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4760     * with a few extra bonuses:
4761     *
4762     * - Supports browsers that do not support rAF by falling back to
4763     *   {@link Component#setTimeout}.
4764     *
4765     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4766     *   bound to the component).
4767     *
4768     * - Automatic cancellation of the rAF callback is handled if the component
4769     *   is disposed before it is called.
4770     *
4771     * @param  {Component~GenericCallback} fn
4772     *         A function that will be bound to this component and executed just
4773     *         before the browser's next repaint.
4774     *
4775     * @return {number}
4776     *         Returns an rAF ID that gets used to identify the timeout. It can
4777     *         also be used in {@link Component#cancelAnimationFrame} to cancel
4778     *         the animation frame callback.
4779     *
4780     * @listens Component#dispose
4781     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4782     */
4783    ;
4784
4785    _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
4786      var _this3 = this;
4787
4788      // Fall back to using a timer.
4789      if (!this.supportsRaf_) {
4790        return this.setTimeout(fn, 1000 / 60);
4791      }
4792
4793      this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function
4794      // eslint-disable-next-line
4795
4796      var id;
4797      fn = bind(this, fn);
4798      id = window$1.requestAnimationFrame(function () {
4799        if (_this3.rafIds_.has(id)) {
4800          _this3.rafIds_["delete"](id);
4801        }
4802
4803        fn();
4804      });
4805      this.rafIds_.add(id);
4806      return id;
4807    }
4808    /**
4809     * Request an animation frame, but only one named animation
4810     * frame will be queued. Another will never be added until
4811     * the previous one finishes.
4812     *
4813     * @param {string} name
4814     *        The name to give this requestAnimationFrame
4815     *
4816     * @param  {Component~GenericCallback} fn
4817     *         A function that will be bound to this component and executed just
4818     *         before the browser's next repaint.
4819     */
4820    ;
4821
4822    _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) {
4823      var _this4 = this;
4824
4825      if (this.namedRafs_.has(name)) {
4826        return;
4827      }
4828
4829      this.clearTimersOnDispose_();
4830      fn = bind(this, fn);
4831      var id = this.requestAnimationFrame(function () {
4832        fn();
4833
4834        if (_this4.namedRafs_.has(name)) {
4835          _this4.namedRafs_["delete"](name);
4836        }
4837      });
4838      this.namedRafs_.set(name, id);
4839      return name;
4840    }
4841    /**
4842     * Cancels a current named animation frame if it exists.
4843     *
4844     * @param {string} name
4845     *        The name of the requestAnimationFrame to cancel.
4846     */
4847    ;
4848
4849    _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) {
4850      if (!this.namedRafs_.has(name)) {
4851        return;
4852      }
4853
4854      this.cancelAnimationFrame(this.namedRafs_.get(name));
4855      this.namedRafs_["delete"](name);
4856    }
4857    /**
4858     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4859     * (rAF).
4860     *
4861     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4862     * use this function instead of `window.cancelAnimationFrame`. If you don't,
4863     * your dispose listener will not get cleaned up until {@link Component#dispose}!
4864     *
4865     * @param {number} id
4866     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4867     *
4868     * @return {number}
4869     *         Returns the rAF ID that was cleared.
4870     *
4871     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4872     */
4873    ;
4874
4875    _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
4876      // Fall back to using a timer.
4877      if (!this.supportsRaf_) {
4878        return this.clearTimeout(id);
4879      }
4880
4881      if (this.rafIds_.has(id)) {
4882        this.rafIds_["delete"](id);
4883        window$1.cancelAnimationFrame(id);
4884      }
4885
4886      return id;
4887    }
4888    /**
4889     * A function to setup `requestAnimationFrame`, `setTimeout`,
4890     * and `setInterval`, clearing on dispose.
4891     *
4892     * > Previously each timer added and removed dispose listeners on it's own.
4893     * For better performance it was decided to batch them all, and use `Set`s
4894     * to track outstanding timer ids.
4895     *
4896     * @private
4897     */
4898    ;
4899
4900    _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() {
4901      var _this5 = this;
4902
4903      if (this.clearingTimersOnDispose_) {
4904        return;
4905      }
4906
4907      this.clearingTimersOnDispose_ = true;
4908      this.one('dispose', function () {
4909        [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(function (_ref) {
4910          var idName = _ref[0],
4911              cancelName = _ref[1];
4912
4913          // for a `Set` key will actually be the value again
4914          // so forEach((val, val) =>` but for maps we want to use
4915          // the key.
4916          _this5[idName].forEach(function (val, key) {
4917            return _this5[cancelName](key);
4918          });
4919        });
4920        _this5.clearingTimersOnDispose_ = false;
4921      });
4922    }
4923    /**
4924     * Register a `Component` with `videojs` given the name and the component.
4925     *
4926     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4927     *         should be registered using {@link Tech.registerTech} or
4928     *         {@link videojs:videojs.registerTech}.
4929     *
4930     * > NOTE: This function can also be seen on videojs as
4931     *         {@link videojs:videojs.registerComponent}.
4932     *
4933     * @param {string} name
4934     *        The name of the `Component` to register.
4935     *
4936     * @param {Component} ComponentToRegister
4937     *        The `Component` class to register.
4938     *
4939     * @return {Component}
4940     *         The `Component` that was registered.
4941     */
4942    ;
4943
4944    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4945      if (typeof name !== 'string' || !name) {
4946        throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
4947      }
4948
4949      var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
4950
4951      var isTech = Tech && Tech.isTech(ComponentToRegister);
4952      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4953
4954      if (isTech || !isComp) {
4955        var reason;
4956
4957        if (isTech) {
4958          reason = 'techs must be registered using Tech.registerTech()';
4959        } else {
4960          reason = 'must be a Component subclass';
4961        }
4962
4963        throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
4964      }
4965
4966      name = toTitleCase(name);
4967
4968      if (!Component.components_) {
4969        Component.components_ = {};
4970      }
4971
4972      var Player = Component.getComponent('Player');
4973
4974      if (name === 'Player' && Player && Player.players) {
4975        var players = Player.players;
4976        var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
4977        // in Players.players. So, we must loop through and verify that the value
4978        // for each item is not null. This allows registration of the Player component
4979        // after all players have been disposed or before any were created.
4980
4981        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4982          return players[pname];
4983        }).every(Boolean)) {
4984          throw new Error('Can not register Player component after player has been created.');
4985        }
4986      }
4987
4988      Component.components_[name] = ComponentToRegister;
4989      Component.components_[toLowerCase(name)] = ComponentToRegister;
4990      return ComponentToRegister;
4991    }
4992    /**
4993     * Get a `Component` based on the name it was registered with.
4994     *
4995     * @param {string} name
4996     *        The Name of the component to get.
4997     *
4998     * @return {Component}
4999     *         The `Component` that got registered under the given name.
5000     *
5001     * @deprecated In `videojs` 6 this will not return `Component`s that were not
5002     *             registered using {@link Component.registerComponent}. Currently we
5003     *             check the global `videojs` object for a `Component` name and
5004     *             return that if it exists.
5005     */
5006    ;
5007
5008    Component.getComponent = function getComponent(name) {
5009      if (!name || !Component.components_) {
5010        return;
5011      }
5012
5013      return Component.components_[name];
5014    };
5015
5016    return Component;
5017  }();
5018  /**
5019   * Whether or not this component supports `requestAnimationFrame`.
5020   *
5021   * This is exposed primarily for testing purposes.
5022   *
5023   * @private
5024   * @type {Boolean}
5025   */
5026
5027
5028  Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
5029  Component.registerComponent('Component', Component);
5030
5031  function _assertThisInitialized(self) {
5032    if (self === void 0) {
5033      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
5034    }
5035
5036    return self;
5037  }
5038
5039  var assertThisInitialized = _assertThisInitialized;
5040
5041  var _typeof_1 = createCommonjsModule(function (module) {
5042    function _typeof(obj) {
5043      "@babel/helpers - typeof";
5044
5045      if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
5046        module.exports = _typeof = function _typeof(obj) {
5047          return typeof obj;
5048        };
5049      } else {
5050        module.exports = _typeof = function _typeof(obj) {
5051          return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
5052        };
5053      }
5054
5055      return _typeof(obj);
5056    }
5057
5058    module.exports = _typeof;
5059  });
5060
5061  var getPrototypeOf = createCommonjsModule(function (module) {
5062    function _getPrototypeOf(o) {
5063      module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
5064        return o.__proto__ || Object.getPrototypeOf(o);
5065      };
5066      return _getPrototypeOf(o);
5067    }
5068
5069    module.exports = _getPrototypeOf;
5070  });
5071
5072  function _inheritsLoose(subClass, superClass) {
5073    subClass.prototype = Object.create(superClass.prototype);
5074    subClass.prototype.constructor = subClass;
5075    subClass.__proto__ = superClass;
5076  }
5077
5078  var inheritsLoose = _inheritsLoose;
5079
5080  /**
5081   * @file browser.js
5082   * @module browser
5083   */
5084  var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
5085  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
5086  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
5087  /**
5088   * Whether or not this device is an iPod.
5089   *
5090   * @static
5091   * @const
5092   * @type {Boolean}
5093   */
5094
5095  var IS_IPOD = /iPod/i.test(USER_AGENT);
5096  /**
5097   * The detected iOS version - or `null`.
5098   *
5099   * @static
5100   * @const
5101   * @type {string|null}
5102   */
5103
5104  var IOS_VERSION = function () {
5105    var match = USER_AGENT.match(/OS (\d+)_/i);
5106
5107    if (match && match[1]) {
5108      return match[1];
5109    }
5110
5111    return null;
5112  }();
5113  /**
5114   * Whether or not this is an Android device.
5115   *
5116   * @static
5117   * @const
5118   * @type {Boolean}
5119   */
5120
5121  var IS_ANDROID = /Android/i.test(USER_AGENT);
5122  /**
5123   * The detected Android version - or `null`.
5124   *
5125   * @static
5126   * @const
5127   * @type {number|string|null}
5128   */
5129
5130  var ANDROID_VERSION = function () {
5131    // This matches Android Major.Minor.Patch versions
5132    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
5133    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
5134
5135    if (!match) {
5136      return null;
5137    }
5138
5139    var major = match[1] && parseFloat(match[1]);
5140    var minor = match[2] && parseFloat(match[2]);
5141
5142    if (major && minor) {
5143      return parseFloat(match[1] + '.' + match[2]);
5144    } else if (major) {
5145      return major;
5146    }
5147
5148    return null;
5149  }();
5150  /**
5151   * Whether or not this is a native Android browser.
5152   *
5153   * @static
5154   * @const
5155   * @type {Boolean}
5156   */
5157
5158  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
5159  /**
5160   * Whether or not this is Mozilla Firefox.
5161   *
5162   * @static
5163   * @const
5164   * @type {Boolean}
5165   */
5166
5167  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
5168  /**
5169   * Whether or not this is Microsoft Edge.
5170   *
5171   * @static
5172   * @const
5173   * @type {Boolean}
5174   */
5175
5176  var IS_EDGE = /Edg/i.test(USER_AGENT);
5177  /**
5178   * Whether or not this is Google Chrome.
5179   *
5180   * This will also be `true` for Chrome on iOS, which will have different support
5181   * as it is actually Safari under the hood.
5182   *
5183   * @static
5184   * @const
5185   * @type {Boolean}
5186   */
5187
5188  var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
5189  /**
5190   * The detected Google Chrome version - or `null`.
5191   *
5192   * @static
5193   * @const
5194   * @type {number|null}
5195   */
5196
5197  var CHROME_VERSION = function () {
5198    var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
5199
5200    if (match && match[2]) {
5201      return parseFloat(match[2]);
5202    }
5203
5204    return null;
5205  }();
5206  /**
5207   * The detected Internet Explorer version - or `null`.
5208   *
5209   * @static
5210   * @const
5211   * @type {number|null}
5212   */
5213
5214  var IE_VERSION = function () {
5215    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
5216    var version = result && parseFloat(result[1]);
5217
5218    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
5219      // IE 11 has a different user agent string than other IE versions
5220      version = 11.0;
5221    }
5222
5223    return version;
5224  }();
5225  /**
5226   * Whether or not this is desktop Safari.
5227   *
5228   * @static
5229   * @const
5230   * @type {Boolean}
5231   */
5232
5233  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
5234  /**
5235   * Whether or not this is a Windows machine.
5236   *
5237   * @static
5238   * @const
5239   * @type {Boolean}
5240   */
5241
5242  var IS_WINDOWS = /Windows/i.test(USER_AGENT);
5243  /**
5244   * Whether or not this device is touch-enabled.
5245   *
5246   * @static
5247   * @const
5248   * @type {Boolean}
5249   */
5250
5251  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
5252  /**
5253   * Whether or not this device is an iPad.
5254   *
5255   * @static
5256   * @const
5257   * @type {Boolean}
5258   */
5259
5260  var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
5261  /**
5262   * Whether or not this device is an iPhone.
5263   *
5264   * @static
5265   * @const
5266   * @type {Boolean}
5267   */
5268  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
5269  // to identify iPhones, we need to exclude iPads.
5270  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
5271
5272  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
5273  /**
5274   * Whether or not this is an iOS device.
5275   *
5276   * @static
5277   * @const
5278   * @type {Boolean}
5279   */
5280
5281  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
5282  /**
5283   * Whether or not this is any flavor of Safari - including iOS.
5284   *
5285   * @static
5286   * @const
5287   * @type {Boolean}
5288   */
5289
5290  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
5291
5292  var browser = /*#__PURE__*/Object.freeze({
5293    __proto__: null,
5294    IS_IPOD: IS_IPOD,
5295    IOS_VERSION: IOS_VERSION,
5296    IS_ANDROID: IS_ANDROID,
5297    ANDROID_VERSION: ANDROID_VERSION,
5298    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
5299    IS_FIREFOX: IS_FIREFOX,
5300    IS_EDGE: IS_EDGE,
5301    IS_CHROME: IS_CHROME,
5302    CHROME_VERSION: CHROME_VERSION,
5303    IE_VERSION: IE_VERSION,
5304    IS_SAFARI: IS_SAFARI,
5305    IS_WINDOWS: IS_WINDOWS,
5306    TOUCH_ENABLED: TOUCH_ENABLED,
5307    IS_IPAD: IS_IPAD,
5308    IS_IPHONE: IS_IPHONE,
5309    IS_IOS: IS_IOS,
5310    IS_ANY_SAFARI: IS_ANY_SAFARI
5311  });
5312
5313  /**
5314   * @file time-ranges.js
5315   * @module time-ranges
5316   */
5317
5318  /**
5319   * Returns the time for the specified index at the start or end
5320   * of a TimeRange object.
5321   *
5322   * @typedef    {Function} TimeRangeIndex
5323   *
5324   * @param      {number} [index=0]
5325   *             The range number to return the time for.
5326   *
5327   * @return     {number}
5328   *             The time offset at the specified index.
5329   *
5330   * @deprecated The index argument must be provided.
5331   *             In the future, leaving it out will throw an error.
5332   */
5333
5334  /**
5335   * An object that contains ranges of time.
5336   *
5337   * @typedef  {Object} TimeRange
5338   *
5339   * @property {number} length
5340   *           The number of time ranges represented by this object.
5341   *
5342   * @property {module:time-ranges~TimeRangeIndex} start
5343   *           Returns the time offset at which a specified time range begins.
5344   *
5345   * @property {module:time-ranges~TimeRangeIndex} end
5346   *           Returns the time offset at which a specified time range ends.
5347   *
5348   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5349   */
5350
5351  /**
5352   * Check if any of the time ranges are over the maximum index.
5353   *
5354   * @private
5355   * @param   {string} fnName
5356   *          The function name to use for logging
5357   *
5358   * @param   {number} index
5359   *          The index to check
5360   *
5361   * @param   {number} maxIndex
5362   *          The maximum possible index
5363   *
5364   * @throws  {Error} if the timeRanges provided are over the maxIndex
5365   */
5366  function rangeCheck(fnName, index, maxIndex) {
5367    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5368      throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
5369    }
5370  }
5371  /**
5372   * Get the time for the specified index at the start or end
5373   * of a TimeRange object.
5374   *
5375   * @private
5376   * @param      {string} fnName
5377   *             The function name to use for logging
5378   *
5379   * @param      {string} valueIndex
5380   *             The property that should be used to get the time. should be
5381   *             'start' or 'end'
5382   *
5383   * @param      {Array} ranges
5384   *             An array of time ranges
5385   *
5386   * @param      {Array} [rangeIndex=0]
5387   *             The index to start the search at
5388   *
5389   * @return     {number}
5390   *             The time that offset at the specified index.
5391   *
5392   * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5393   * @throws     {Error} if rangeIndex is more than the length of ranges
5394   */
5395
5396
5397  function getRange(fnName, valueIndex, ranges, rangeIndex) {
5398    rangeCheck(fnName, rangeIndex, ranges.length - 1);
5399    return ranges[rangeIndex][valueIndex];
5400  }
5401  /**
5402   * Create a time range object given ranges of time.
5403   *
5404   * @private
5405   * @param   {Array} [ranges]
5406   *          An array of time ranges.
5407   */
5408
5409
5410  function createTimeRangesObj(ranges) {
5411    if (ranges === undefined || ranges.length === 0) {
5412      return {
5413        length: 0,
5414        start: function start() {
5415          throw new Error('This TimeRanges object is empty');
5416        },
5417        end: function end() {
5418          throw new Error('This TimeRanges object is empty');
5419        }
5420      };
5421    }
5422
5423    return {
5424      length: ranges.length,
5425      start: getRange.bind(null, 'start', 0, ranges),
5426      end: getRange.bind(null, 'end', 1, ranges)
5427    };
5428  }
5429  /**
5430   * Create a `TimeRange` object which mimics an
5431   * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5432   *
5433   * @param {number|Array[]} start
5434   *        The start of a single range (a number) or an array of ranges (an
5435   *        array of arrays of two numbers each).
5436   *
5437   * @param {number} end
5438   *        The end of a single range. Cannot be used with the array form of
5439   *        the `start` argument.
5440   */
5441
5442
5443  function createTimeRanges(start, end) {
5444    if (Array.isArray(start)) {
5445      return createTimeRangesObj(start);
5446    } else if (start === undefined || end === undefined) {
5447      return createTimeRangesObj();
5448    }
5449
5450    return createTimeRangesObj([[start, end]]);
5451  }
5452
5453  /**
5454   * @file buffer.js
5455   * @module buffer
5456   */
5457  /**
5458   * Compute the percentage of the media that has been buffered.
5459   *
5460   * @param {TimeRange} buffered
5461   *        The current `TimeRange` object representing buffered time ranges
5462   *
5463   * @param {number} duration
5464   *        Total duration of the media
5465   *
5466   * @return {number}
5467   *         Percent buffered of the total duration in decimal form.
5468   */
5469
5470  function bufferedPercent(buffered, duration) {
5471    var bufferedDuration = 0;
5472    var start;
5473    var end;
5474
5475    if (!duration) {
5476      return 0;
5477    }
5478
5479    if (!buffered || !buffered.length) {
5480      buffered = createTimeRanges(0, 0);
5481    }
5482
5483    for (var i = 0; i < buffered.length; i++) {
5484      start = buffered.start(i);
5485      end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
5486
5487      if (end > duration) {
5488        end = duration;
5489      }
5490
5491      bufferedDuration += end - start;
5492    }
5493
5494    return bufferedDuration / duration;
5495  }
5496
5497  /**
5498   * @file fullscreen-api.js
5499   * @module fullscreen-api
5500   * @private
5501   */
5502  /**
5503   * Store the browser-specific methods for the fullscreen API.
5504   *
5505   * @type {Object}
5506   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
5507   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
5508   */
5509
5510  var FullscreenApi = {
5511    prefixed: true
5512  }; // browser API methods
5513
5514  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
5515  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
5516  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
5517  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
5518  var specApi = apiMap[0];
5519  var browserApi; // determine the supported set of functions
5520
5521  for (var i = 0; i < apiMap.length; i++) {
5522    // check for exitFullscreen function
5523    if (apiMap[i][1] in document) {
5524      browserApi = apiMap[i];
5525      break;
5526    }
5527  } // map the browser API names to the spec API names
5528
5529
5530  if (browserApi) {
5531    for (var _i = 0; _i < browserApi.length; _i++) {
5532      FullscreenApi[specApi[_i]] = browserApi[_i];
5533    }
5534
5535    FullscreenApi.prefixed = browserApi[0] !== specApi[0];
5536  }
5537
5538  /**
5539   * @file media-error.js
5540   */
5541  /**
5542   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5543   *
5544   * @param {number|string|Object|MediaError} value
5545   *        This can be of multiple types:
5546   *        - number: should be a standard error code
5547   *        - string: an error message (the code will be 0)
5548   *        - Object: arbitrary properties
5549   *        - `MediaError` (native): used to populate a video.js `MediaError` object
5550   *        - `MediaError` (video.js): will return itself if it's already a
5551   *          video.js `MediaError` object.
5552   *
5553   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5554   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5555   *
5556   * @class MediaError
5557   */
5558
5559  function MediaError(value) {
5560    // Allow redundant calls to this constructor to avoid having `instanceof`
5561    // checks peppered around the code.
5562    if (value instanceof MediaError) {
5563      return value;
5564    }
5565
5566    if (typeof value === 'number') {
5567      this.code = value;
5568    } else if (typeof value === 'string') {
vendor: 9,266 bytes, lines 5569-5861
5569      // default code is zero, so this is a custom error
5570      this.message = value;
5571    } else if (isObject(value)) {
5572      // We assign the `code` property manually because native `MediaError` objects
5573      // do not expose it as an own/enumerable property of the object.
5574      if (typeof value.code === 'number') {
5575        this.code = value.code;
5576      }
5577
5578      assign(this, value);
5579    }
5580
5581    if (!this.message) {
5582      this.message = MediaError.defaultMessages[this.code] || '';
5583    }
5584  }
5585  /**
5586   * The error code that refers two one of the defined `MediaError` types
5587   *
5588   * @type {Number}
5589   */
5590
5591
5592  MediaError.prototype.code = 0;
5593  /**
5594   * An optional message that to show with the error. Message is not part of the HTML5
5595   * video spec but allows for more informative custom errors.
5596   *
5597   * @type {String}
5598   */
5599
5600  MediaError.prototype.message = '';
5601  /**
5602   * An optional status code that can be set by plugins to allow even more detail about
5603   * the error. For example a plugin might provide a specific HTTP status code and an
5604   * error message for that code. Then when the plugin gets that error this class will
5605   * know how to display an error message for it. This allows a custom message to show
5606   * up on the `Player` error overlay.
5607   *
5608   * @type {Array}
5609   */
5610
5611  MediaError.prototype.status = null;
5612  /**
5613   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5614   * specification listed under {@link MediaError} for more information.
5615   *
5616   * @enum {array}
5617   * @readonly
5618   * @property {string} 0 - MEDIA_ERR_CUSTOM
5619   * @property {string} 1 - MEDIA_ERR_ABORTED
5620   * @property {string} 2 - MEDIA_ERR_NETWORK
5621   * @property {string} 3 - MEDIA_ERR_DECODE
5622   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5623   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5624   */
5625
5626  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5627  /**
5628   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5629   *
5630   * @type {Array}
5631   * @constant
5632   */
5633
5634  MediaError.defaultMessages = {
5635    1: 'You aborted the media playback',
5636    2: 'A network error caused the media download to fail part-way.',
5637    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5638    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5639    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5640  }; // Add types as properties on MediaError
5641  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5642
5643  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5644    MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
5645
5646    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5647  } // jsdocs for instance/static members added above
5648
5649  var tuple = SafeParseTuple;
5650
5651  function SafeParseTuple(obj, reviver) {
5652    var json;
5653    var error = null;
5654
5655    try {
5656      json = JSON.parse(obj, reviver);
5657    } catch (err) {
5658      error = err;
5659    }
5660
5661    return [error, json];
5662  }
5663
5664  /**
5665   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5666   *
5667   * @param  {Object}  value
5668   *         An object that may or may not be `Promise`-like.
5669   *
5670   * @return {boolean}
5671   *         Whether or not the object is `Promise`-like.
5672   */
5673  function isPromise(value) {
5674    return value !== undefined && value !== null && typeof value.then === 'function';
5675  }
5676  /**
5677   * Silence a Promise-like object.
5678   *
5679   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5680   * play promise" rejection error messages.
5681   *
5682   * @param  {Object} value
5683   *         An object that may or may not be `Promise`-like.
5684   */
5685
5686  function silencePromise(value) {
5687    if (isPromise(value)) {
5688      value.then(null, function (e) {});
5689    }
5690  }
5691
5692  /**
5693   * @file text-track-list-converter.js Utilities for capturing text track state and
5694   * re-creating tracks based on a capture.
5695   *
5696   * @module text-track-list-converter
5697   */
5698
5699  /**
5700   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5701   * represents the {@link TextTrack}'s state.
5702   *
5703   * @param {TextTrack} track
5704   *        The text track to query.
5705   *
5706   * @return {Object}
5707   *         A serializable javascript representation of the TextTrack.
5708   * @private
5709   */
5710  var trackToJson_ = function trackToJson_(track) {
5711    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5712      if (track[prop]) {
5713        acc[prop] = track[prop];
5714      }
5715
5716      return acc;
5717    }, {
5718      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5719        return {
5720          startTime: cue.startTime,
5721          endTime: cue.endTime,
5722          text: cue.text,
5723          id: cue.id
5724        };
5725      })
5726    });
5727    return ret;
5728  };
5729  /**
5730   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5731   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5732   * {@link text-track-list-converter:jsonToTextTracks}.
5733   *
5734   * @param {Tech} tech
5735   *        The tech object to query
5736   *
5737   * @return {Array}
5738   *         A serializable javascript representation of the {@link Tech}s
5739   *         {@link TextTrackList}.
5740   */
5741
5742
5743  var textTracksToJson = function textTracksToJson(tech) {
5744    var trackEls = tech.$$('track');
5745    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5746      return t.track;
5747    });
5748    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5749      var json = trackToJson_(trackEl.track);
5750
5751      if (trackEl.src) {
5752        json.src = trackEl.src;
5753      }
5754
5755      return json;
5756    });
5757    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5758      return trackObjs.indexOf(track) === -1;
5759    }).map(trackToJson_));
5760  };
5761  /**
5762   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5763   * object {@link TextTrack} representations.
5764   *
5765   * @param {Array} json
5766   *        An array of `TextTrack` representation objects, like those that would be
5767   *        produced by `textTracksToJson`.
5768   *
5769   * @param {Tech} tech
5770   *        The `Tech` to create the `TextTrack`s on.
5771   */
5772
5773
5774  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5775    json.forEach(function (track) {
5776      var addedTrack = tech.addRemoteTextTrack(track).track;
5777
5778      if (!track.src && track.cues) {
5779        track.cues.forEach(function (cue) {
5780          return addedTrack.addCue(cue);
5781        });
5782      }
5783    });
5784    return tech.textTracks();
5785  };
5786
5787  var textTrackConverter = {
5788    textTracksToJson: textTracksToJson,
5789    jsonToTextTracks: jsonToTextTracks,
5790    trackToJson_: trackToJson_
5791  };
5792
5793  var keycode = createCommonjsModule(function (module, exports) {
5794    // Source: http://jsfiddle.net/vWx8V/
5795    // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
5796
5797    /**
5798     * Conenience method returns corresponding value for given keyName or keyCode.
5799     *
5800     * @param {Mixed} keyCode {Number} or keyName {String}
5801     * @return {Mixed}
5802     * @api public
5803     */
5804    function keyCode(searchInput) {
5805      // Keyboard Events
5806      if (searchInput && 'object' === typeof searchInput) {
5807        var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
5808        if (hasKeyCode) searchInput = hasKeyCode;
5809      } // Numbers
5810
5811
5812      if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
5813
5814      var search = String(searchInput); // check codes
5815
5816      var foundNamedKey = codes[search.toLowerCase()];
5817      if (foundNamedKey) return foundNamedKey; // check aliases
5818
5819      var foundNamedKey = aliases[search.toLowerCase()];
5820      if (foundNamedKey) return foundNamedKey; // weird character?
5821
5822      if (search.length === 1) return search.charCodeAt(0);
5823      return undefined;
5824    }
5825    /**
5826     * Compares a keyboard event with a given keyCode or keyName.
5827     *
5828     * @param {Event} event Keyboard event that should be tested
5829     * @param {Mixed} keyCode {Number} or keyName {String}
5830     * @return {Boolean}
5831     * @api public
5832     */
5833
5834
5835    keyCode.isEventKey = function isEventKey(event, nameOrCode) {
5836      if (event && 'object' === typeof event) {
5837        var keyCode = event.which || event.keyCode || event.charCode;
5838
5839        if (keyCode === null || keyCode === undefined) {
5840          return false;
5841        }
5842
5843        if (typeof nameOrCode === 'string') {
5844          // check codes
5845          var foundNamedKey = codes[nameOrCode.toLowerCase()];
5846
5847          if (foundNamedKey) {
5848            return foundNamedKey === keyCode;
5849          } // check aliases
5850
5851
5852          var foundNamedKey = aliases[nameOrCode.toLowerCase()];
5853
5854          if (foundNamedKey) {
5855            return foundNamedKey === keyCode;
5856          }
5857        } else if (typeof nameOrCode === 'number') {
5858          return nameOrCode === keyCode;
5859        }
5860
5861        return false;
5862      }
5863    };
5864
5865    exports = module.exports = keyCode;
5866    /**
5867     * Get by name
5868     *
5869     *   exports.code['enter'] // => 13
5870     */
5871
5872    var codes = exports.code = exports.codes = {
5873      'backspace': 8,
5874      'tab': 9,
5875      'enter': 13,
5876      'shift': 16,
5877      'ctrl': 17,
5878      'alt': 18,
5879      'pause/break': 19,
5880      'caps lock': 20,
5881      'esc': 27,
5882      'space': 32,
5883      'page up': 33,
5884      'page down': 34,
5885      'end': 35,
5886      'home': 36,
5887      'left': 37,
5888      'up': 38,
5889      'right': 39,
5890      'down': 40,
5891      'insert': 45,
5892      'delete': 46,
5893      'command': 91,
5894      'left command': 91,
5895      'right command': 93,
5896      'numpad *': 106,
5897      'numpad +': 107,
5898      'numpad -': 109,
5899      'numpad .': 110,
5900      'numpad /': 111,
5901      'num lock': 144,
5902      'scroll lock': 145,
5903      'my computer': 182,
5904      'my calculator': 183,
5905      ';': 186,
5906      '=': 187,
5907      ',': 188,
5908      '-': 189,
5909      '.': 190,
5910      '/': 191,
5911      '`': 192,
5912      '[': 219,
5913      '\\': 220,
5914      ']': 221,
5915      "'": 222
5916    }; // Helper aliases
5917
5918    var aliases = exports.aliases = {
5919      'windows': 91,
5920      '⇧': 16,
5921      '⌥': 18,
5922      '⌃': 17,
5923      '⌘': 91,
5924      'ctl': 17,
5925      'control': 17,
5926      'option': 18,
5927      'pause': 19,
5928      'break': 19,
5929      'caps': 20,
5930      'return': 13,
5931      'escape': 27,
5932      'spc': 32,
5933      'spacebar': 32,
5934      'pgup': 33,
5935      'pgdn': 34,
5936      'ins': 45,
5937      'del': 46,
5938      'cmd': 91
5939    };
5940    /*!
5941     * Programatically add the following
5942     */
5943    // lower case chars
5944
5945    for (i = 97; i < 123; i++) {
5946      codes[String.fromCharCode(i)] = i - 32;
5947    } // numbers
5948
5949
5950    for (var i = 48; i < 58; i++) {
5951      codes[i - 48] = i;
5952    } // function keys
5953
5954
5955    for (i = 1; i < 13; i++) {
5956      codes['f' + i] = i + 111;
5957    } // numpad keys
5958
5959
5960    for (i = 0; i < 10; i++) {
5961      codes['numpad ' + i] = i + 96;
5962    }
5963    /**
5964     * Get by code
5965     *
5966     *   exports.name[13] // => 'Enter'
5967     */
5968
5969
5970    var names = exports.names = exports.title = {}; // title for backward compat
5971    // Create reverse mapping
5972
5973    for (i in codes) {
5974      names[codes[i]] = i;
5975    } // Add aliases
5976
5977
5978    for (var alias in aliases) {
5979      codes[alias] = aliases[alias];
5980    }
5981  });
5982  var keycode_1 = keycode.code;
5983  var keycode_2 = keycode.codes;
5984  var keycode_3 = keycode.aliases;
5985  var keycode_4 = keycode.names;
5986  var keycode_5 = keycode.title;
5987
5988  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5989  /**
5990   * The `ModalDialog` displays over the video and its controls, which blocks
5991   * interaction with the player until it is closed.
5992   *
5993   * Modal dialogs include a "Close" button and will close when that button
5994   * is activated - or when ESC is pressed anywhere.
5995   *
5996   * @extends Component
5997   */
5998
5999  var ModalDialog = /*#__PURE__*/function (_Component) {
6000    inheritsLoose(ModalDialog, _Component);
6001
6002    /**
6003     * Create an instance of this class.
6004     *
6005     * @param {Player} player
6006     *        The `Player` that this class should be attached to.
6007     *
6008     * @param {Object} [options]
6009     *        The key/value store of player options.
6010     *
6011     * @param {Mixed} [options.content=undefined]
6012     *        Provide customized content for this modal.
6013     *
6014     * @param {string} [options.description]
6015     *        A text description for the modal, primarily for accessibility.
6016     *
6017     * @param {boolean} [options.fillAlways=false]
6018     *        Normally, modals are automatically filled only the first time
6019     *        they open. This tells the modal to refresh its content
6020     *        every time it opens.
6021     *
6022     * @param {string} [options.label]
6023     *        A text label for the modal, primarily for accessibility.
6024     *
6025     * @param {boolean} [options.pauseOnOpen=true]
6026     *        If `true`, playback will will be paused if playing when
6027     *        the modal opens, and resumed when it closes.
6028     *
6029     * @param {boolean} [options.temporary=true]
6030     *        If `true`, the modal can only be opened once; it will be
6031     *        disposed as soon as it's closed.
6032     *
6033     * @param {boolean} [options.uncloseable=false]
6034     *        If `true`, the user will not be able to close the modal
6035     *        through the UI in the normal ways. Programmatic closing is
6036     *        still possible.
6037     */
6038    function ModalDialog(player, options) {
6039      var _this;
6040
6041      _this = _Component.call(this, player, options) || this;
6042      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
vendor: 72,018 bytes, lines 6043-8689
6043
6044      _this.closeable(!_this.options_.uncloseable);
6045
6046      _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
6047      // because we only want the contents of the modal in the contentEl
6048      // (not the UI elements like the close button).
6049
6050
6051      _this.contentEl_ = createEl('div', {
6052        className: MODAL_CLASS_NAME + "-content"
6053      }, {
6054        role: 'document'
6055      });
6056      _this.descEl_ = createEl('p', {
6057        className: MODAL_CLASS_NAME + "-description vjs-control-text",
6058        id: _this.el().getAttribute('aria-describedby')
6059      });
6060      textContent(_this.descEl_, _this.description());
6061
6062      _this.el_.appendChild(_this.descEl_);
6063
6064      _this.el_.appendChild(_this.contentEl_);
6065
6066      return _this;
6067    }
6068    /**
6069     * Create the `ModalDialog`'s DOM element
6070     *
6071     * @return {Element}
6072     *         The DOM element that gets created.
6073     */
6074
6075
6076    var _proto = ModalDialog.prototype;
6077
6078    _proto.createEl = function createEl() {
6079      return _Component.prototype.createEl.call(this, 'div', {
6080        className: this.buildCSSClass(),
6081        tabIndex: -1
6082      }, {
6083        'aria-describedby': this.id() + "_description",
6084        'aria-hidden': 'true',
6085        'aria-label': this.label(),
6086        'role': 'dialog'
6087      });
6088    };
6089
6090    _proto.dispose = function dispose() {
6091      this.contentEl_ = null;
6092      this.descEl_ = null;
6093      this.previouslyActiveEl_ = null;
6094
6095      _Component.prototype.dispose.call(this);
6096    }
6097    /**
6098     * Builds the default DOM `className`.
6099     *
6100     * @return {string}
6101     *         The DOM `className` for this object.
6102     */
6103    ;
6104
6105    _proto.buildCSSClass = function buildCSSClass() {
6106      return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
6107    }
6108    /**
6109     * Returns the label string for this modal. Primarily used for accessibility.
6110     *
6111     * @return {string}
6112     *         the localized or raw label of this modal.
6113     */
6114    ;
6115
6116    _proto.label = function label() {
6117      return this.localize(this.options_.label || 'Modal Window');
6118    }
6119    /**
6120     * Returns the description string for this modal. Primarily used for
6121     * accessibility.
6122     *
6123     * @return {string}
6124     *         The localized or raw description of this modal.
6125     */
6126    ;
6127
6128    _proto.description = function description() {
6129      var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
6130
6131      if (this.closeable()) {
6132        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
6133      }
6134
6135      return desc;
6136    }
6137    /**
6138     * Opens the modal.
6139     *
6140     * @fires ModalDialog#beforemodalopen
6141     * @fires ModalDialog#modalopen
6142     */
6143    ;
6144
6145    _proto.open = function open() {
6146      if (!this.opened_) {
6147        var player = this.player();
6148        /**
6149          * Fired just before a `ModalDialog` is opened.
6150          *
6151          * @event ModalDialog#beforemodalopen
6152          * @type {EventTarget~Event}
6153          */
6154
6155        this.trigger('beforemodalopen');
6156        this.opened_ = true; // Fill content if the modal has never opened before and
6157        // never been filled.
6158
6159        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
6160          this.fill();
6161        } // If the player was playing, pause it and take note of its previously
6162        // playing state.
6163
6164
6165        this.wasPlaying_ = !player.paused();
6166
6167        if (this.options_.pauseOnOpen && this.wasPlaying_) {
6168          player.pause();
6169        }
6170
6171        this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled.
6172
6173        this.hadControls_ = player.controls();
6174        player.controls(false);
6175        this.show();
6176        this.conditionalFocus_();
6177        this.el().setAttribute('aria-hidden', 'false');
6178        /**
6179          * Fired just after a `ModalDialog` is opened.
6180          *
6181          * @event ModalDialog#modalopen
6182          * @type {EventTarget~Event}
6183          */
6184
6185        this.trigger('modalopen');
6186        this.hasBeenOpened_ = true;
6187      }
6188    }
6189    /**
6190     * If the `ModalDialog` is currently open or closed.
6191     *
6192     * @param  {boolean} [value]
6193     *         If given, it will open (`true`) or close (`false`) the modal.
6194     *
6195     * @return {boolean}
6196     *         the current open state of the modaldialog
6197     */
6198    ;
6199
6200    _proto.opened = function opened(value) {
6201      if (typeof value === 'boolean') {
6202        this[value ? 'open' : 'close']();
6203      }
6204
6205      return this.opened_;
6206    }
6207    /**
6208     * Closes the modal, does nothing if the `ModalDialog` is
6209     * not open.
6210     *
6211     * @fires ModalDialog#beforemodalclose
6212     * @fires ModalDialog#modalclose
6213     */
6214    ;
6215
6216    _proto.close = function close() {
6217      if (!this.opened_) {
6218        return;
6219      }
6220
6221      var player = this.player();
6222      /**
6223        * Fired just before a `ModalDialog` is closed.
6224        *
6225        * @event ModalDialog#beforemodalclose
6226        * @type {EventTarget~Event}
6227        */
6228
6229      this.trigger('beforemodalclose');
6230      this.opened_ = false;
6231
6232      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6233        player.play();
6234      }
6235
6236      this.off('keydown', this.handleKeyDown);
6237
6238      if (this.hadControls_) {
6239        player.controls(true);
6240      }
6241
6242      this.hide();
6243      this.el().setAttribute('aria-hidden', 'true');
6244      /**
6245        * Fired just after a `ModalDialog` is closed.
6246        *
6247        * @event ModalDialog#modalclose
6248        * @type {EventTarget~Event}
6249        */
6250
6251      this.trigger('modalclose');
6252      this.conditionalBlur_();
6253
6254      if (this.options_.temporary) {
6255        this.dispose();
6256      }
6257    }
6258    /**
6259     * Check to see if the `ModalDialog` is closeable via the UI.
6260     *
6261     * @param  {boolean} [value]
6262     *         If given as a boolean, it will set the `closeable` option.
6263     *
6264     * @return {boolean}
6265     *         Returns the final value of the closable option.
6266     */
6267    ;
6268
6269    _proto.closeable = function closeable(value) {
6270      if (typeof value === 'boolean') {
6271        var closeable = this.closeable_ = !!value;
6272        var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
6273
6274        if (closeable && !close) {
6275          // The close button should be a child of the modal - not its
6276          // content element, so temporarily change the content element.
6277          var temp = this.contentEl_;
6278          this.contentEl_ = this.el_;
6279          close = this.addChild('closeButton', {
6280            controlText: 'Close Modal Dialog'
6281          });
6282          this.contentEl_ = temp;
6283          this.on(close, 'close', this.close);
6284        } // If this is being made uncloseable and has a close button, remove it.
6285
6286
6287        if (!closeable && close) {
6288          this.off(close, 'close', this.close);
6289          this.removeChild(close);
6290          close.dispose();
6291        }
6292      }
6293
6294      return this.closeable_;
6295    }
6296    /**
6297     * Fill the modal's content element with the modal's "content" option.
6298     * The content element will be emptied before this change takes place.
6299     */
6300    ;
6301
6302    _proto.fill = function fill() {
6303      this.fillWith(this.content());
6304    }
6305    /**
6306     * Fill the modal's content element with arbitrary content.
6307     * The content element will be emptied before this change takes place.
6308     *
6309     * @fires ModalDialog#beforemodalfill
6310     * @fires ModalDialog#modalfill
6311     *
6312     * @param {Mixed} [content]
6313     *        The same rules apply to this as apply to the `content` option.
6314     */
6315    ;
6316
6317    _proto.fillWith = function fillWith(content) {
6318      var contentEl = this.contentEl();
6319      var parentEl = contentEl.parentNode;
6320      var nextSiblingEl = contentEl.nextSibling;
6321      /**
6322        * Fired just before a `ModalDialog` is filled with content.
6323        *
6324        * @event ModalDialog#beforemodalfill
6325        * @type {EventTarget~Event}
6326        */
6327
6328      this.trigger('beforemodalfill');
6329      this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
6330      // manipulation to avoid modifying the live DOM multiple times.
6331
6332      parentEl.removeChild(contentEl);
6333      this.empty();
6334      insertContent(contentEl, content);
6335      /**
6336       * Fired just after a `ModalDialog` is filled with content.
6337       *
6338       * @event ModalDialog#modalfill
6339       * @type {EventTarget~Event}
6340       */
6341
6342      this.trigger('modalfill'); // Re-inject the re-filled content element.
6343
6344      if (nextSiblingEl) {
6345        parentEl.insertBefore(contentEl, nextSiblingEl);
6346      } else {
6347        parentEl.appendChild(contentEl);
6348      } // make sure that the close button is last in the dialog DOM
6349
6350
6351      var closeButton = this.getChild('closeButton');
6352
6353      if (closeButton) {
6354        parentEl.appendChild(closeButton.el_);
6355      }
6356    }
6357    /**
6358     * Empties the content element. This happens anytime the modal is filled.
6359     *
6360     * @fires ModalDialog#beforemodalempty
6361     * @fires ModalDialog#modalempty
6362     */
6363    ;
6364
6365    _proto.empty = function empty() {
6366      /**
6367      * Fired just before a `ModalDialog` is emptied.
6368      *
6369      * @event ModalDialog#beforemodalempty
6370      * @type {EventTarget~Event}
6371      */
6372      this.trigger('beforemodalempty');
6373      emptyEl(this.contentEl());
6374      /**
6375      * Fired just after a `ModalDialog` is emptied.
6376      *
6377      * @event ModalDialog#modalempty
6378      * @type {EventTarget~Event}
6379      */
6380
6381      this.trigger('modalempty');
6382    }
6383    /**
6384     * Gets or sets the modal content, which gets normalized before being
6385     * rendered into the DOM.
6386     *
6387     * This does not update the DOM or fill the modal, but it is called during
6388     * that process.
6389     *
6390     * @param  {Mixed} [value]
6391     *         If defined, sets the internal content value to be used on the
6392     *         next call(s) to `fill`. This value is normalized before being
6393     *         inserted. To "clear" the internal content value, pass `null`.
6394     *
6395     * @return {Mixed}
6396     *         The current content of the modal dialog
6397     */
6398    ;
6399
6400    _proto.content = function content(value) {
6401      if (typeof value !== 'undefined') {
6402        this.content_ = value;
6403      }
6404
6405      return this.content_;
6406    }
6407    /**
6408     * conditionally focus the modal dialog if focus was previously on the player.
6409     *
6410     * @private
6411     */
6412    ;
6413
6414    _proto.conditionalFocus_ = function conditionalFocus_() {
6415      var activeEl = document.activeElement;
6416      var playerEl = this.player_.el_;
6417      this.previouslyActiveEl_ = null;
6418
6419      if (playerEl.contains(activeEl) || playerEl === activeEl) {
6420        this.previouslyActiveEl_ = activeEl;
6421        this.focus();
6422      }
6423    }
6424    /**
6425     * conditionally blur the element and refocus the last focused element
6426     *
6427     * @private
6428     */
6429    ;
6430
6431    _proto.conditionalBlur_ = function conditionalBlur_() {
6432      if (this.previouslyActiveEl_) {
6433        this.previouslyActiveEl_.focus();
6434        this.previouslyActiveEl_ = null;
6435      }
6436    }
6437    /**
6438     * Keydown handler. Attached when modal is focused.
6439     *
6440     * @listens keydown
6441     */
6442    ;
6443
6444    _proto.handleKeyDown = function handleKeyDown(event) {
6445      // Do not allow keydowns to reach out of the modal dialog.
6446      event.stopPropagation();
6447
6448      if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6449        event.preventDefault();
6450        this.close();
6451        return;
6452      } // exit early if it isn't a tab key
6453
6454
6455      if (!keycode.isEventKey(event, 'Tab')) {
6456        return;
6457      }
6458
6459      var focusableEls = this.focusableEls_();
6460      var activeEl = this.el_.querySelector(':focus');
6461      var focusIndex;
6462
6463      for (var i = 0; i < focusableEls.length; i++) {
6464        if (activeEl === focusableEls[i]) {
6465          focusIndex = i;
6466          break;
6467        }
6468      }
6469
6470      if (document.activeElement === this.el_) {
6471        focusIndex = 0;
6472      }
6473
6474      if (event.shiftKey && focusIndex === 0) {
6475        focusableEls[focusableEls.length - 1].focus();
6476        event.preventDefault();
6477      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6478        focusableEls[0].focus();
6479        event.preventDefault();
6480      }
6481    }
6482    /**
6483     * get all focusable elements
6484     *
6485     * @private
6486     */
6487    ;
6488
6489    _proto.focusableEls_ = function focusableEls_() {
6490      var allChildren = this.el_.querySelectorAll('*');
6491      return Array.prototype.filter.call(allChildren, function (child) {
6492        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');
6493      });
6494    };
6495
6496    return ModalDialog;
6497  }(Component);
6498  /**
6499   * Default options for `ModalDialog` default options.
6500   *
6501   * @type {Object}
6502   * @private
6503   */
6504
6505
6506  ModalDialog.prototype.options_ = {
6507    pauseOnOpen: true,
6508    temporary: true
6509  };
6510  Component.registerComponent('ModalDialog', ModalDialog);
6511
6512  /**
6513   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6514   * {@link VideoTrackList}
6515   *
6516   * @extends EventTarget
6517   */
6518
6519  var TrackList = /*#__PURE__*/function (_EventTarget) {
6520    inheritsLoose(TrackList, _EventTarget);
6521
6522    /**
6523     * Create an instance of this class
6524     *
6525     * @param {Track[]} tracks
6526     *        A list of tracks to initialize the list with.
6527     *
6528     * @abstract
6529     */
6530    function TrackList(tracks) {
6531      var _this;
6532
6533      if (tracks === void 0) {
6534        tracks = [];
6535      }
6536
6537      _this = _EventTarget.call(this) || this;
6538      _this.tracks_ = [];
6539      /**
6540       * @memberof TrackList
6541       * @member {number} length
6542       *         The current number of `Track`s in the this Trackist.
6543       * @instance
6544       */
6545
6546      Object.defineProperty(assertThisInitialized(_this), 'length', {
6547        get: function get() {
6548          return this.tracks_.length;
6549        }
6550      });
6551
6552      for (var i = 0; i < tracks.length; i++) {
6553        _this.addTrack(tracks[i]);
6554      }
6555
6556      return _this;
6557    }
6558    /**
6559     * Add a {@link Track} to the `TrackList`
6560     *
6561     * @param {Track} track
6562     *        The audio, video, or text track to add to the list.
6563     *
6564     * @fires TrackList#addtrack
6565     */
6566
6567
6568    var _proto = TrackList.prototype;
6569
6570    _proto.addTrack = function addTrack(track) {
6571      var index = this.tracks_.length;
6572
6573      if (!('' + index in this)) {
6574        Object.defineProperty(this, index, {
6575          get: function get() {
6576            return this.tracks_[index];
6577          }
6578        });
6579      } // Do not add duplicate tracks
6580
6581
6582      if (this.tracks_.indexOf(track) === -1) {
6583        this.tracks_.push(track);
6584        /**
6585         * Triggered when a track is added to a track list.
6586         *
6587         * @event TrackList#addtrack
6588         * @type {EventTarget~Event}
6589         * @property {Track} track
6590         *           A reference to track that was added.
6591         */
6592
6593        this.trigger({
6594          track: track,
6595          type: 'addtrack',
6596          target: this
6597        });
6598      }
6599    }
6600    /**
6601     * Remove a {@link Track} from the `TrackList`
6602     *
6603     * @param {Track} rtrack
6604     *        The audio, video, or text track to remove from the list.
6605     *
6606     * @fires TrackList#removetrack
6607     */
6608    ;
6609
6610    _proto.removeTrack = function removeTrack(rtrack) {
6611      var track;
6612
6613      for (var i = 0, l = this.length; i < l; i++) {
6614        if (this[i] === rtrack) {
6615          track = this[i];
6616
6617          if (track.off) {
6618            track.off();
6619          }
6620
6621          this.tracks_.splice(i, 1);
6622          break;
6623        }
6624      }
6625
6626      if (!track) {
6627        return;
6628      }
6629      /**
6630       * Triggered when a track is removed from track list.
6631       *
6632       * @event TrackList#removetrack
6633       * @type {EventTarget~Event}
6634       * @property {Track} track
6635       *           A reference to track that was removed.
6636       */
6637
6638
6639      this.trigger({
6640        track: track,
6641        type: 'removetrack',
6642        target: this
6643      });
6644    }
6645    /**
6646     * Get a Track from the TrackList by a tracks id
6647     *
6648     * @param {string} id - the id of the track to get
6649     * @method getTrackById
6650     * @return {Track}
6651     * @private
6652     */
6653    ;
6654
6655    _proto.getTrackById = function getTrackById(id) {
6656      var result = null;
6657
6658      for (var i = 0, l = this.length; i < l; i++) {
6659        var track = this[i];
6660
6661        if (track.id === id) {
6662          result = track;
6663          break;
6664        }
6665      }
6666
6667      return result;
6668    };
6669
6670    return TrackList;
6671  }(EventTarget);
6672  /**
6673   * Triggered when a different track is selected/enabled.
6674   *
6675   * @event TrackList#change
6676   * @type {EventTarget~Event}
6677   */
6678
6679  /**
6680   * Events that can be called with on + eventName. See {@link EventHandler}.
6681   *
6682   * @property {Object} TrackList#allowedEvents_
6683   * @private
6684   */
6685
6686
6687  TrackList.prototype.allowedEvents_ = {
6688    change: 'change',
6689    addtrack: 'addtrack',
6690    removetrack: 'removetrack'
6691  }; // emulate attribute EventHandler support to allow for feature detection
6692
6693  for (var event in TrackList.prototype.allowedEvents_) {
6694    TrackList.prototype['on' + event] = null;
6695  }
6696
6697  /**
6698   * Anywhere we call this function we diverge from the spec
6699   * as we only support one enabled audiotrack at a time
6700   *
6701   * @param {AudioTrackList} list
6702   *        list to work on
6703   *
6704   * @param {AudioTrack} track
6705   *        The track to skip
6706   *
6707   * @private
6708   */
6709
6710  var disableOthers = function disableOthers(list, track) {
6711    for (var i = 0; i < list.length; i++) {
6712      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6713        continue;
6714      } // another audio track is enabled, disable it
6715
6716
6717      list[i].enabled = false;
6718    }
6719  };
6720  /**
6721   * The current list of {@link AudioTrack} for a media file.
6722   *
6723   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6724   * @extends TrackList
6725   */
6726
6727
6728  var AudioTrackList = /*#__PURE__*/function (_TrackList) {
6729    inheritsLoose(AudioTrackList, _TrackList);
6730
6731    /**
6732     * Create an instance of this class.
6733     *
6734     * @param {AudioTrack[]} [tracks=[]]
6735     *        A list of `AudioTrack` to instantiate the list with.
6736     */
6737    function AudioTrackList(tracks) {
6738      var _this;
6739
6740      if (tracks === void 0) {
6741        tracks = [];
6742      }
6743
6744      // make sure only 1 track is enabled
6745      // sorted from last index to first index
6746      for (var i = tracks.length - 1; i >= 0; i--) {
6747        if (tracks[i].enabled) {
6748          disableOthers(tracks, tracks[i]);
6749          break;
6750        }
6751      }
6752
6753      _this = _TrackList.call(this, tracks) || this;
6754      _this.changing_ = false;
6755      return _this;
6756    }
6757    /**
6758     * Add an {@link AudioTrack} to the `AudioTrackList`.
6759     *
6760     * @param {AudioTrack} track
6761     *        The AudioTrack to add to the list
6762     *
6763     * @fires TrackList#addtrack
6764     */
6765
6766
6767    var _proto = AudioTrackList.prototype;
6768
6769    _proto.addTrack = function addTrack(track) {
6770      var _this2 = this;
6771
6772      if (track.enabled) {
6773        disableOthers(this, track);
6774      }
6775
6776      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6777
6778
6779      if (!track.addEventListener) {
6780        return;
6781      }
6782
6783      track.enabledChange_ = function () {
6784        // when we are disabling other tracks (since we don't support
6785        // more than one track at a time) we will set changing_
6786        // to true so that we don't trigger additional change events
6787        if (_this2.changing_) {
6788          return;
6789        }
6790
6791        _this2.changing_ = true;
6792        disableOthers(_this2, track);
6793        _this2.changing_ = false;
6794
6795        _this2.trigger('change');
6796      };
6797      /**
6798       * @listens AudioTrack#enabledchange
6799       * @fires TrackList#change
6800       */
6801
6802
6803      track.addEventListener('enabledchange', track.enabledChange_);
6804    };
6805
6806    _proto.removeTrack = function removeTrack(rtrack) {
6807      _TrackList.prototype.removeTrack.call(this, rtrack);
6808
6809      if (rtrack.removeEventListener && rtrack.enabledChange_) {
6810        rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
6811        rtrack.enabledChange_ = null;
6812      }
6813    };
6814
6815    return AudioTrackList;
6816  }(TrackList);
6817
6818  /**
6819   * Un-select all other {@link VideoTrack}s that are selected.
6820   *
6821   * @param {VideoTrackList} list
6822   *        list to work on
6823   *
6824   * @param {VideoTrack} track
6825   *        The track to skip
6826   *
6827   * @private
6828   */
6829
6830  var disableOthers$1 = function disableOthers(list, track) {
6831    for (var i = 0; i < list.length; i++) {
6832      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6833        continue;
6834      } // another video track is enabled, disable it
6835
6836
6837      list[i].selected = false;
6838    }
6839  };
6840  /**
6841   * The current list of {@link VideoTrack} for a video.
6842   *
6843   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6844   * @extends TrackList
6845   */
6846
6847
6848  var VideoTrackList = /*#__PURE__*/function (_TrackList) {
6849    inheritsLoose(VideoTrackList, _TrackList);
6850
6851    /**
6852     * Create an instance of this class.
6853     *
6854     * @param {VideoTrack[]} [tracks=[]]
6855     *        A list of `VideoTrack` to instantiate the list with.
6856     */
6857    function VideoTrackList(tracks) {
6858      var _this;
6859
6860      if (tracks === void 0) {
6861        tracks = [];
6862      }
6863
6864      // make sure only 1 track is enabled
6865      // sorted from last index to first index
6866      for (var i = tracks.length - 1; i >= 0; i--) {
6867        if (tracks[i].selected) {
6868          disableOthers$1(tracks, tracks[i]);
6869          break;
6870        }
6871      }
6872
6873      _this = _TrackList.call(this, tracks) || this;
6874      _this.changing_ = false;
6875      /**
6876       * @member {number} VideoTrackList#selectedIndex
6877       *         The current index of the selected {@link VideoTrack`}.
6878       */
6879
6880      Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', {
6881        get: function get() {
6882          for (var _i = 0; _i < this.length; _i++) {
6883            if (this[_i].selected) {
6884              return _i;
6885            }
6886          }
6887
6888          return -1;
6889        },
6890        set: function set() {}
6891      });
6892      return _this;
6893    }
6894    /**
6895     * Add a {@link VideoTrack} to the `VideoTrackList`.
6896     *
6897     * @param {VideoTrack} track
6898     *        The VideoTrack to add to the list
6899     *
6900     * @fires TrackList#addtrack
6901     */
6902
6903
6904    var _proto = VideoTrackList.prototype;
6905
6906    _proto.addTrack = function addTrack(track) {
6907      var _this2 = this;
6908
6909      if (track.selected) {
6910        disableOthers$1(this, track);
6911      }
6912
6913      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6914
6915
6916      if (!track.addEventListener) {
6917        return;
6918      }
6919
6920      track.selectedChange_ = function () {
6921        if (_this2.changing_) {
6922          return;
6923        }
6924
6925        _this2.changing_ = true;
6926        disableOthers$1(_this2, track);
6927        _this2.changing_ = false;
6928
6929        _this2.trigger('change');
6930      };
6931      /**
6932       * @listens VideoTrack#selectedchange
6933       * @fires TrackList#change
6934       */
6935
6936
6937      track.addEventListener('selectedchange', track.selectedChange_);
6938    };
6939
6940    _proto.removeTrack = function removeTrack(rtrack) {
6941      _TrackList.prototype.removeTrack.call(this, rtrack);
6942
6943      if (rtrack.removeEventListener && rtrack.selectedChange_) {
6944        rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
6945        rtrack.selectedChange_ = null;
6946      }
6947    };
6948
6949    return VideoTrackList;
6950  }(TrackList);
6951
6952  /**
6953   * The current list of {@link TextTrack} for a media file.
6954   *
6955   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6956   * @extends TrackList
6957   */
6958
6959  var TextTrackList = /*#__PURE__*/function (_TrackList) {
6960    inheritsLoose(TextTrackList, _TrackList);
6961
6962    function TextTrackList() {
6963      return _TrackList.apply(this, arguments) || this;
6964    }
6965
6966    var _proto = TextTrackList.prototype;
6967
6968    /**
6969     * Add a {@link TextTrack} to the `TextTrackList`
6970     *
6971     * @param {TextTrack} track
6972     *        The text track to add to the list.
6973     *
6974     * @fires TrackList#addtrack
6975     */
6976    _proto.addTrack = function addTrack(track) {
6977      var _this = this;
6978
6979      _TrackList.prototype.addTrack.call(this, track);
6980
6981      if (!this.queueChange_) {
6982        this.queueChange_ = function () {
6983          return _this.queueTrigger('change');
6984        };
6985      }
6986
6987      if (!this.triggerSelectedlanguagechange) {
6988        this.triggerSelectedlanguagechange_ = function () {
6989          return _this.trigger('selectedlanguagechange');
6990        };
6991      }
6992      /**
6993       * @listens TextTrack#modechange
6994       * @fires TrackList#change
6995       */
6996
6997
6998      track.addEventListener('modechange', this.queueChange_);
6999      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
7000
7001      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
7002        track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
7003      }
7004    };
7005
7006    _proto.removeTrack = function removeTrack(rtrack) {
7007      _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
7008
7009
7010      if (rtrack.removeEventListener) {
7011        if (this.queueChange_) {
7012          rtrack.removeEventListener('modechange', this.queueChange_);
7013        }
7014
7015        if (this.selectedlanguagechange_) {
7016          rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
7017        }
7018      }
7019    };
7020
7021    return TextTrackList;
7022  }(TrackList);
7023
7024  /**
7025   * @file html-track-element-list.js
7026   */
7027
7028  /**
7029   * The current list of {@link HtmlTrackElement}s.
7030   */
7031  var HtmlTrackElementList = /*#__PURE__*/function () {
7032    /**
7033     * Create an instance of this class.
7034     *
7035     * @param {HtmlTrackElement[]} [tracks=[]]
7036     *        A list of `HtmlTrackElement` to instantiate the list with.
7037     */
7038    function HtmlTrackElementList(trackElements) {
7039      if (trackElements === void 0) {
7040        trackElements = [];
7041      }
7042
7043      this.trackElements_ = [];
7044      /**
7045       * @memberof HtmlTrackElementList
7046       * @member {number} length
7047       *         The current number of `Track`s in the this Trackist.
7048       * @instance
7049       */
7050
7051      Object.defineProperty(this, 'length', {
7052        get: function get() {
7053          return this.trackElements_.length;
7054        }
7055      });
7056
7057      for (var i = 0, length = trackElements.length; i < length; i++) {
7058        this.addTrackElement_(trackElements[i]);
7059      }
7060    }
7061    /**
7062     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
7063     *
7064     * @param {HtmlTrackElement} trackElement
7065     *        The track element to add to the list.
7066     *
7067     * @private
7068     */
7069
7070
7071    var _proto = HtmlTrackElementList.prototype;
7072
7073    _proto.addTrackElement_ = function addTrackElement_(trackElement) {
7074      var index = this.trackElements_.length;
7075
7076      if (!('' + index in this)) {
7077        Object.defineProperty(this, index, {
7078          get: function get() {
7079            return this.trackElements_[index];
7080          }
7081        });
7082      } // Do not add duplicate elements
7083
7084
7085      if (this.trackElements_.indexOf(trackElement) === -1) {
7086        this.trackElements_.push(trackElement);
7087      }
7088    }
7089    /**
7090     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
7091     * {@link TextTrack}.
7092     *
7093     * @param {TextTrack} track
7094     *        The track associated with a track element.
7095     *
7096     * @return {HtmlTrackElement|undefined}
7097     *         The track element that was found or undefined.
7098     *
7099     * @private
7100     */
7101    ;
7102
7103    _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
7104      var trackElement_;
7105
7106      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
7107        if (track === this.trackElements_[i].track) {
7108          trackElement_ = this.trackElements_[i];
7109          break;
7110        }
7111      }
7112
7113      return trackElement_;
7114    }
7115    /**
7116     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
7117     *
7118     * @param {HtmlTrackElement} trackElement
7119     *        The track element to remove from the list.
7120     *
7121     * @private
7122     */
7123    ;
7124
7125    _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
7126      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
7127        if (trackElement === this.trackElements_[i]) {
7128          if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
7129            this.trackElements_[i].track.off();
7130          }
7131
7132          if (typeof this.trackElements_[i].off === 'function') {
7133            this.trackElements_[i].off();
7134          }
7135
7136          this.trackElements_.splice(i, 1);
7137          break;
7138        }
7139      }
7140    };
7141
7142    return HtmlTrackElementList;
7143  }();
7144
7145  /**
7146   * @file text-track-cue-list.js
7147   */
7148
7149  /**
7150   * @typedef {Object} TextTrackCueList~TextTrackCue
7151   *
7152   * @property {string} id
7153   *           The unique id for this text track cue
7154   *
7155   * @property {number} startTime
7156   *           The start time for this text track cue
7157   *
7158   * @property {number} endTime
7159   *           The end time for this text track cue
7160   *
7161   * @property {boolean} pauseOnExit
7162   *           Pause when the end time is reached if true.
7163   *
7164   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
7165   */
7166
7167  /**
7168   * A List of TextTrackCues.
7169   *
7170   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
7171   */
7172  var TextTrackCueList = /*#__PURE__*/function () {
7173    /**
7174     * Create an instance of this class..
7175     *
7176     * @param {Array} cues
7177     *        A list of cues to be initialized with
7178     */
7179    function TextTrackCueList(cues) {
7180      TextTrackCueList.prototype.setCues_.call(this, cues);
7181      /**
7182       * @memberof TextTrackCueList
7183       * @member {number} length
7184       *         The current number of `TextTrackCue`s in the TextTrackCueList.
7185       * @instance
7186       */
7187
7188      Object.defineProperty(this, 'length', {
7189        get: function get() {
7190          return this.length_;
7191        }
7192      });
7193    }
7194    /**
7195     * A setter for cues in this list. Creates getters
7196     * an an index for the cues.
7197     *
7198     * @param {Array} cues
7199     *        An array of cues to set
7200     *
7201     * @private
7202     */
7203
7204
7205    var _proto = TextTrackCueList.prototype;
7206
7207    _proto.setCues_ = function setCues_(cues) {
7208      var oldLength = this.length || 0;
7209      var i = 0;
7210      var l = cues.length;
7211      this.cues_ = cues;
7212      this.length_ = cues.length;
7213
7214      var defineProp = function defineProp(index) {
7215        if (!('' + index in this)) {
7216          Object.defineProperty(this, '' + index, {
7217            get: function get() {
7218              return this.cues_[index];
7219            }
7220          });
7221        }
7222      };
7223
7224      if (oldLength < l) {
7225        i = oldLength;
7226
7227        for (; i < l; i++) {
7228          defineProp.call(this, i);
7229        }
7230      }
7231    }
7232    /**
7233     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
7234     *
7235     * @param {string} id
7236     *        The id of the cue that should be searched for.
7237     *
7238     * @return {TextTrackCueList~TextTrackCue|null}
7239     *         A single cue or null if none was found.
7240     */
7241    ;
7242
7243    _proto.getCueById = function getCueById(id) {
7244      var result = null;
7245
7246      for (var i = 0, l = this.length; i < l; i++) {
7247        var cue = this[i];
7248
7249        if (cue.id === id) {
7250          result = cue;
7251          break;
7252        }
7253      }
7254
7255      return result;
7256    };
7257
7258    return TextTrackCueList;
7259  }();
7260
7261  /**
7262   * @file track-kinds.js
7263   */
7264
7265  /**
7266   * All possible `VideoTrackKind`s
7267   *
7268   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7269   * @typedef VideoTrack~Kind
7270   * @enum
7271   */
7272  var VideoTrackKind = {
7273    alternative: 'alternative',
7274    captions: 'captions',
7275    main: 'main',
7276    sign: 'sign',
7277    subtitles: 'subtitles',
7278    commentary: 'commentary'
7279  };
7280  /**
7281   * All possible `AudioTrackKind`s
7282   *
7283   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7284   * @typedef AudioTrack~Kind
7285   * @enum
7286   */
7287
7288  var AudioTrackKind = {
7289    'alternative': 'alternative',
7290    'descriptions': 'descriptions',
7291    'main': 'main',
7292    'main-desc': 'main-desc',
7293    'translation': 'translation',
7294    'commentary': 'commentary'
7295  };
7296  /**
7297   * All possible `TextTrackKind`s
7298   *
7299   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7300   * @typedef TextTrack~Kind
7301   * @enum
7302   */
7303
7304  var TextTrackKind = {
7305    subtitles: 'subtitles',
7306    captions: 'captions',
7307    descriptions: 'descriptions',
7308    chapters: 'chapters',
7309    metadata: 'metadata'
7310  };
7311  /**
7312   * All possible `TextTrackMode`s
7313   *
7314   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7315   * @typedef TextTrack~Mode
7316   * @enum
7317   */
7318
7319  var TextTrackMode = {
7320    disabled: 'disabled',
7321    hidden: 'hidden',
7322    showing: 'showing'
7323  };
7324
7325  /**
7326   * A Track class that contains all of the common functionality for {@link AudioTrack},
7327   * {@link VideoTrack}, and {@link TextTrack}.
7328   *
7329   * > Note: This class should not be used directly
7330   *
7331   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7332   * @extends EventTarget
7333   * @abstract
7334   */
7335
7336  var Track = /*#__PURE__*/function (_EventTarget) {
7337    inheritsLoose(Track, _EventTarget);
7338
7339    /**
7340     * Create an instance of this class.
7341     *
7342     * @param {Object} [options={}]
7343     *        Object of option names and values
7344     *
7345     * @param {string} [options.kind='']
7346     *        A valid kind for the track type you are creating.
7347     *
7348     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7349     *        A unique id for this AudioTrack.
7350     *
7351     * @param {string} [options.label='']
7352     *        The menu label for this track.
7353     *
7354     * @param {string} [options.language='']
7355     *        A valid two character language code.
7356     *
7357     * @abstract
7358     */
7359    function Track(options) {
7360      var _this;
7361
7362      if (options === void 0) {
7363        options = {};
7364      }
7365
7366      _this = _EventTarget.call(this) || this;
7367      var trackProps = {
7368        id: options.id || 'vjs_track_' + newGUID(),
7369        kind: options.kind || '',
7370        label: options.label || '',
7371        language: options.language || ''
7372      };
7373      /**
7374       * @memberof Track
7375       * @member {string} id
7376       *         The id of this track. Cannot be changed after creation.
7377       * @instance
7378       *
7379       * @readonly
7380       */
7381
7382      /**
7383       * @memberof Track
7384       * @member {string} kind
7385       *         The kind of track that this is. Cannot be changed after creation.
7386       * @instance
7387       *
7388       * @readonly
7389       */
7390
7391      /**
7392       * @memberof Track
7393       * @member {string} label
7394       *         The label of this track. Cannot be changed after creation.
7395       * @instance
7396       *
7397       * @readonly
7398       */
7399
7400      /**
7401       * @memberof Track
7402       * @member {string} language
7403       *         The two letter language code for this track. Cannot be changed after
7404       *         creation.
7405       * @instance
7406       *
7407       * @readonly
7408       */
7409
7410      var _loop = function _loop(key) {
7411        Object.defineProperty(assertThisInitialized(_this), key, {
7412          get: function get() {
7413            return trackProps[key];
7414          },
7415          set: function set() {}
7416        });
7417      };
7418
7419      for (var key in trackProps) {
7420        _loop(key);
7421      }
7422
7423      return _this;
7424    }
7425
7426    return Track;
7427  }(EventTarget);
7428
7429  /**
7430   * @file url.js
7431   * @module url
7432   */
7433  /**
7434   * @typedef {Object} url:URLObject
7435   *
7436   * @property {string} protocol
7437   *           The protocol of the url that was parsed.
7438   *
7439   * @property {string} hostname
7440   *           The hostname of the url that was parsed.
7441   *
7442   * @property {string} port
7443   *           The port of the url that was parsed.
7444   *
7445   * @property {string} pathname
7446   *           The pathname of the url that was parsed.
7447   *
7448   * @property {string} search
7449   *           The search query of the url that was parsed.
7450   *
7451   * @property {string} hash
7452   *           The hash of the url that was parsed.
7453   *
7454   * @property {string} host
7455   *           The host of the url that was parsed.
7456   */
7457
7458  /**
7459   * Resolve and parse the elements of a URL.
7460   *
7461   * @function
7462   * @param    {String} url
7463   *           The url to parse
7464   *
7465   * @return   {url:URLObject}
7466   *           An object of url details
7467   */
7468
7469  var parseUrl = function parseUrl(url) {
7470    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
7471
7472    var a = document.createElement('a');
7473    a.href = url; // IE8 (and 9?) Fix
7474    // ie8 doesn't parse the URL correctly until the anchor is actually
7475    // added to the body, and an innerHTML is needed to trigger the parsing
7476
7477    var addToBody = a.host === '' && a.protocol !== 'file:';
7478    var div;
7479
7480    if (addToBody) {
7481      div = document.createElement('div');
7482      div.innerHTML = "<a href=\"" + url + "\"></a>";
7483      a = div.firstChild; // prevent the div from affecting layout
7484
7485      div.setAttribute('style', 'display:none; position:absolute;');
7486      document.body.appendChild(div);
7487    } // Copy the specific URL properties to a new object
7488    // This is also needed for IE8 because the anchor loses its
7489    // properties when it's removed from the dom
7490
7491
7492    var details = {};
7493
7494    for (var i = 0; i < props.length; i++) {
7495      details[props[i]] = a[props[i]];
7496    } // IE9 adds the port to the host property unlike everyone else. If
7497    // a port identifier is added for standard ports, strip it.
7498
7499
7500    if (details.protocol === 'http:') {
7501      details.host = details.host.replace(/:80$/, '');
7502    }
7503
7504    if (details.protocol === 'https:') {
7505      details.host = details.host.replace(/:443$/, '');
7506    }
7507
7508    if (!details.protocol) {
7509      details.protocol = window$1.location.protocol;
7510    }
7511
7512    if (addToBody) {
7513      document.body.removeChild(div);
7514    }
7515
7516    return details;
7517  };
7518  /**
7519   * Get absolute version of relative URL. Used to tell Flash the correct URL.
7520   *
7521   * @function
7522   * @param    {string} url
7523   *           URL to make absolute
7524   *
7525   * @return   {string}
7526   *           Absolute URL
7527   *
7528   * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7529   */
7530
7531  var getAbsoluteURL = function getAbsoluteURL(url) {
7532    // Check if absolute URL
7533    if (!url.match(/^https?:\/\//)) {
7534      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
7535      var div = document.createElement('div');
7536      div.innerHTML = "<a href=\"" + url + "\">x</a>";
7537      url = div.firstChild.href;
7538    }
7539
7540    return url;
7541  };
7542  /**
7543   * Returns the extension of the passed file name. It will return an empty string
7544   * if passed an invalid path.
7545   *
7546   * @function
7547   * @param    {string} path
7548   *           The fileName path like '/path/to/file.mp4'
7549   *
7550   * @return  {string}
7551   *           The extension in lower case or an empty string if no
7552   *           extension could be found.
7553   */
7554
7555  var getFileExtension = function getFileExtension(path) {
7556    if (typeof path === 'string') {
7557      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7558      var pathParts = splitPathRe.exec(path);
7559
7560      if (pathParts) {
7561        return pathParts.pop().toLowerCase();
7562      }
7563    }
7564
7565    return '';
7566  };
7567  /**
7568   * Returns whether the url passed is a cross domain request or not.
7569   *
7570   * @function
7571   * @param    {string} url
7572   *           The url to check.
7573   *
7574   * @param    {Object} [winLoc]
7575   *           the domain to check the url against, defaults to window.location
7576   *
7577   * @param    {string} [winLoc.protocol]
7578   *           The window location protocol defaults to window.location.protocol
7579   *
7580   * @param    {string} [winLoc.host]
7581   *           The window location host defaults to window.location.host
7582   *
7583   * @return   {boolean}
7584   *           Whether it is a cross domain request or not.
7585   */
7586
7587  var isCrossOrigin = function isCrossOrigin(url, winLoc) {
7588    if (winLoc === void 0) {
7589      winLoc = window$1.location;
7590    }
7591
7592    var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
7593
7594    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
7595    // IE8 doesn't know location.origin, so we won't rely on it here
7596
7597    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7598    return crossOrigin;
7599  };
7600
7601  var Url = /*#__PURE__*/Object.freeze({
7602    __proto__: null,
7603    parseUrl: parseUrl,
7604    getAbsoluteURL: getAbsoluteURL,
7605    getFileExtension: getFileExtension,
7606    isCrossOrigin: isCrossOrigin
7607  });
7608
7609  var isFunction_1 = isFunction;
7610  var toString$1 = Object.prototype.toString;
7611
7612  function isFunction(fn) {
7613    var string = toString$1.call(fn);
7614    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
7615    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7616  }
7617
7618  /**
7619   * @license
7620   * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
7621   * Copyright (c) 2014 David Björklund
7622   * Available under the MIT license
7623   * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
7624   */
7625
7626
7627  var parseHeaders = function parseHeaders(headers) {
7628    var result = {};
7629
7630    if (!headers) {
7631      return result;
7632    }
7633
7634    headers.trim().split('\n').forEach(function (row) {
7635      var index = row.indexOf(':');
7636      var key = row.slice(0, index).trim().toLowerCase();
7637      var value = row.slice(index + 1).trim();
7638
7639      if (typeof result[key] === 'undefined') {
7640        result[key] = value;
7641      } else if (Array.isArray(result[key])) {
7642        result[key].push(value);
7643      } else {
7644        result[key] = [result[key], value];
7645      }
7646    });
7647    return result;
7648  };
7649
7650  var xhr = createXHR; // Allow use of default import syntax in TypeScript
7651
7652  var default_1 = createXHR;
7653  createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
7654  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
7655  forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
7656    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7657      options = initParams(uri, options, callback);
7658      options.method = method.toUpperCase();
7659      return _createXHR(options);
7660    };
7661  });
7662
7663  function forEachArray(array, iterator) {
7664    for (var i = 0; i < array.length; i++) {
7665      iterator(array[i]);
7666    }
7667  }
7668
7669  function isEmpty(obj) {
7670    for (var i in obj) {
7671      if (obj.hasOwnProperty(i)) return false;
7672    }
7673
7674    return true;
7675  }
7676
7677  function initParams(uri, options, callback) {
7678    var params = uri;
7679
7680    if (isFunction_1(options)) {
7681      callback = options;
7682
7683      if (typeof uri === "string") {
7684        params = {
7685          uri: uri
7686        };
7687      }
7688    } else {
7689      params = _extends_1({}, options, {
7690        uri: uri
7691      });
7692    }
7693
7694    params.callback = callback;
7695    return params;
7696  }
7697
7698  function createXHR(uri, options, callback) {
7699    options = initParams(uri, options, callback);
7700    return _createXHR(options);
7701  }
7702
7703  function _createXHR(options) {
7704    if (typeof options.callback === "undefined") {
7705      throw new Error("callback argument missing");
7706    }
7707
7708    var called = false;
7709
7710    var callback = function cbOnce(err, response, body) {
7711      if (!called) {
7712        called = true;
7713        options.callback(err, response, body);
7714      }
7715    };
7716
7717    function readystatechange() {
7718      if (xhr.readyState === 4) {
7719        setTimeout(loadFunc, 0);
7720      }
7721    }
7722
7723    function getBody() {
7724      // Chrome with requestType=blob throws errors arround when even testing access to responseText
7725      var body = undefined;
7726
7727      if (xhr.response) {
7728        body = xhr.response;
7729      } else {
7730        body = xhr.responseText || getXml(xhr);
7731      }
7732
7733      if (isJson) {
7734        try {
7735          body = JSON.parse(body);
7736        } catch (e) {}
7737      }
7738
7739      return body;
7740    }
7741
7742    function errorFunc(evt) {
7743      clearTimeout(timeoutTimer);
7744
7745      if (!(evt instanceof Error)) {
7746        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7747      }
7748
7749      evt.statusCode = 0;
7750      return callback(evt, failureResponse);
7751    } // will load the data & process the response in a special response object
7752
7753
7754    function loadFunc() {
7755      if (aborted) return;
7756      var status;
7757      clearTimeout(timeoutTimer);
7758
7759      if (options.useXDR && xhr.status === undefined) {
7760        //IE8 CORS GET successful response doesn't have a status field, but body is fine
7761        status = 200;
7762      } else {
7763        status = xhr.status === 1223 ? 204 : xhr.status;
7764      }
7765
7766      var response = failureResponse;
7767      var err = null;
7768
7769      if (status !== 0) {
7770        response = {
7771          body: getBody(),
7772          statusCode: status,
7773          method: method,
7774          headers: {},
7775          url: uri,
7776          rawRequest: xhr
7777        };
7778
7779        if (xhr.getAllResponseHeaders) {
7780          //remember xhr can in fact be XDR for CORS in IE
7781          response.headers = parseHeaders(xhr.getAllResponseHeaders());
7782        }
7783      } else {
7784        err = new Error("Internal XMLHttpRequest Error");
7785      }
7786
7787      return callback(err, response, response.body);
7788    }
7789
7790    var xhr = options.xhr || null;
7791
7792    if (!xhr) {
7793      if (options.cors || options.useXDR) {
7794        xhr = new createXHR.XDomainRequest();
7795      } else {
7796        xhr = new createXHR.XMLHttpRequest();
7797      }
7798    }
7799
7800    var key;
7801    var aborted;
7802    var uri = xhr.url = options.uri || options.url;
7803    var method = xhr.method = options.method || "GET";
7804    var body = options.body || options.data;
7805    var headers = xhr.headers = options.headers || {};
7806    var sync = !!options.sync;
7807    var isJson = false;
7808    var timeoutTimer;
7809    var failureResponse = {
7810      body: undefined,
7811      headers: {},
7812      statusCode: 0,
7813      method: method,
7814      url: uri,
7815      rawRequest: xhr
7816    };
7817
7818    if ("json" in options && options.json !== false) {
7819      isJson = true;
7820      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7821
7822      if (method !== "GET" && method !== "HEAD") {
7823        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7824
7825        body = JSON.stringify(options.json === true ? body : options.json);
7826      }
7827    }
7828
7829    xhr.onreadystatechange = readystatechange;
7830    xhr.onload = loadFunc;
7831    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
7832
7833    xhr.onprogress = function () {// IE must die
7834    };
7835
7836    xhr.onabort = function () {
7837      aborted = true;
7838    };
7839
7840    xhr.ontimeout = errorFunc;
7841    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
7842
7843    if (!sync) {
7844      xhr.withCredentials = !!options.withCredentials;
7845    } // Cannot set timeout with sync request
7846    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7847    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7848
7849
7850    if (!sync && options.timeout > 0) {
7851      timeoutTimer = setTimeout(function () {
7852        if (aborted) return;
7853        aborted = true; //IE9 may still call readystatechange
7854
7855        xhr.abort("timeout");
7856        var e = new Error("XMLHttpRequest timeout");
7857        e.code = "ETIMEDOUT";
7858        errorFunc(e);
7859      }, options.timeout);
7860    }
7861
7862    if (xhr.setRequestHeader) {
7863      for (key in headers) {
7864        if (headers.hasOwnProperty(key)) {
7865          xhr.setRequestHeader(key, headers[key]);
7866        }
7867      }
7868    } else if (options.headers && !isEmpty(options.headers)) {
7869      throw new Error("Headers cannot be set on an XDomainRequest object");
7870    }
7871
7872    if ("responseType" in options) {
7873      xhr.responseType = options.responseType;
7874    }
7875
7876    if ("beforeSend" in options && typeof options.beforeSend === "function") {
7877      options.beforeSend(xhr);
7878    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7879    // XMLHttpRequest spec says to pass null as body to indicate no body
7880    // See https://github.com/naugtur/xhr/issues/100.
7881
7882
7883    xhr.send(body || null);
7884    return xhr;
7885  }
7886
7887  function getXml(xhr) {
7888    // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
7889    // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
7890    try {
7891      if (xhr.responseType === "document") {
7892        return xhr.responseXML;
7893      }
7894
7895      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7896
7897      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7898        return xhr.responseXML;
7899      }
7900    } catch (e) {}
7901
7902    return null;
7903  }
7904
7905  function noop() {}
7906  xhr["default"] = default_1;
7907
7908  /**
7909   * Takes a webvtt file contents and parses it into cues
7910   *
7911   * @param {string} srcContent
7912   *        webVTT file contents
7913   *
7914   * @param {TextTrack} track
7915   *        TextTrack to add cues to. Cues come from the srcContent.
7916   *
7917   * @private
7918   */
7919
7920  var parseCues = function parseCues(srcContent, track) {
7921    var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
7922    var errors = [];
7923
7924    parser.oncue = function (cue) {
7925      track.addCue(cue);
7926    };
7927
7928    parser.onparsingerror = function (error) {
7929      errors.push(error);
7930    };
7931
7932    parser.onflush = function () {
7933      track.trigger({
7934        type: 'loadeddata',
7935        target: track
7936      });
7937    };
7938
7939    parser.parse(srcContent);
7940
7941    if (errors.length > 0) {
7942      if (window$1.console && window$1.console.groupCollapsed) {
7943        window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
7944      }
7945
7946      errors.forEach(function (error) {
7947        return log.error(error);
7948      });
7949
7950      if (window$1.console && window$1.console.groupEnd) {
7951        window$1.console.groupEnd();
7952      }
7953    }
7954
7955    parser.flush();
7956  };
7957  /**
7958   * Load a `TextTrack` from a specified url.
7959   *
7960   * @param {string} src
7961   *        Url to load track from.
7962   *
7963   * @param {TextTrack} track
7964   *        Track to add cues to. Comes from the content at the end of `url`.
7965   *
7966   * @private
7967   */
7968
7969
7970  var loadTrack = function loadTrack(src, track) {
7971    var opts = {
7972      uri: src
7973    };
7974    var crossOrigin = isCrossOrigin(src);
7975
7976    if (crossOrigin) {
7977      opts.cors = crossOrigin;
7978    }
7979
7980    var withCredentials = track.tech_.crossOrigin() === 'use-credentials';
7981
7982    if (withCredentials) {
7983      opts.withCredentials = withCredentials;
7984    }
7985
7986    xhr(opts, bind(this, function (err, response, responseBody) {
7987      if (err) {
7988        return log.error(err, response);
7989      }
7990
7991      track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7992      // NOTE: this is only used for the alt/video.novtt.js build
7993
7994      if (typeof window$1.WebVTT !== 'function') {
7995        if (track.tech_) {
7996          // to prevent use before define eslint error, we define loadHandler
7997          // as a let here
7998          track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) {
7999            if (event.type === 'vttjserror') {
8000              log.error("vttjs failed to load, stopping trying to process " + track.src);
8001              return;
8002            }
8003
8004            return parseCues(responseBody, track);
8005          });
8006        }
8007      } else {
8008        parseCues(responseBody, track);
8009      }
8010    }));
8011  };
8012  /**
8013   * A representation of a single `TextTrack`.
8014   *
8015   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
8016   * @extends Track
8017   */
8018
8019
8020  var TextTrack = /*#__PURE__*/function (_Track) {
8021    inheritsLoose(TextTrack, _Track);
8022
8023    /**
8024     * Create an instance of this class.
8025     *
8026     * @param {Object} options={}
8027     *        Object of option names and values
8028     *
8029     * @param {Tech} options.tech
8030     *        A reference to the tech that owns this TextTrack.
8031     *
8032     * @param {TextTrack~Kind} [options.kind='subtitles']
8033     *        A valid text track kind.
8034     *
8035     * @param {TextTrack~Mode} [options.mode='disabled']
8036     *        A valid text track mode.
8037     *
8038     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8039     *        A unique id for this TextTrack.
8040     *
8041     * @param {string} [options.label='']
8042     *        The menu label for this track.
8043     *
8044     * @param {string} [options.language='']
8045     *        A valid two character language code.
8046     *
8047     * @param {string} [options.srclang='']
8048     *        A valid two character language code. An alternative, but deprioritized
8049     *        version of `options.language`
8050     *
8051     * @param {string} [options.src]
8052     *        A url to TextTrack cues.
8053     *
8054     * @param {boolean} [options.default]
8055     *        If this track should default to on or off.
8056     */
8057    function TextTrack(options) {
8058      var _this;
8059
8060      if (options === void 0) {
8061        options = {};
8062      }
8063
8064      if (!options.tech) {
8065        throw new Error('A tech was not provided.');
8066      }
8067
8068      var settings = mergeOptions(options, {
8069        kind: TextTrackKind[options.kind] || 'subtitles',
8070        language: options.language || options.srclang || ''
8071      });
8072      var mode = TextTrackMode[settings.mode] || 'disabled';
8073      var default_ = settings["default"];
8074
8075      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
8076        mode = 'hidden';
8077      }
8078
8079      _this = _Track.call(this, settings) || this;
8080      _this.tech_ = settings.tech;
8081      _this.cues_ = [];
8082      _this.activeCues_ = [];
8083      _this.preload_ = _this.tech_.preloadTextTracks !== false;
8084      var cues = new TextTrackCueList(_this.cues_);
8085      var activeCues = new TextTrackCueList(_this.activeCues_);
8086      var changed = false;
8087      var timeupdateHandler = bind(assertThisInitialized(_this), function () {
8088        // Accessing this.activeCues for the side-effects of updating itself
8089        // due to its nature as a getter function. Do not remove or cues will
8090        // stop updating!
8091        // Use the setter to prevent deletion from uglify (pure_getters rule)
8092        this.activeCues = this.activeCues;
8093
8094        if (changed) {
8095          this.trigger('cuechange');
8096          changed = false;
8097        }
8098      });
8099
8100      if (mode !== 'disabled') {
8101        _this.tech_.ready(function () {
8102          _this.tech_.on('timeupdate', timeupdateHandler);
8103        }, true);
8104      }
8105
8106      Object.defineProperties(assertThisInitialized(_this), {
8107        /**
8108         * @memberof TextTrack
8109         * @member {boolean} default
8110         *         If this track was set to be on or off by default. Cannot be changed after
8111         *         creation.
8112         * @instance
8113         *
8114         * @readonly
8115         */
8116        "default": {
8117          get: function get() {
8118            return default_;
8119          },
8120          set: function set() {}
8121        },
8122
8123        /**
8124         * @memberof TextTrack
8125         * @member {string} mode
8126         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
8127         *         not be set if setting to an invalid mode.
8128         * @instance
8129         *
8130         * @fires TextTrack#modechange
8131         */
8132        mode: {
8133          get: function get() {
8134            return mode;
8135          },
8136          set: function set(newMode) {
8137            var _this2 = this;
8138
8139            if (!TextTrackMode[newMode]) {
8140              return;
8141            }
8142
8143            mode = newMode;
8144
8145            if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
8146              // On-demand load.
8147              loadTrack(this.src, this);
8148            }
8149
8150            if (mode !== 'disabled') {
8151              this.tech_.ready(function () {
8152                _this2.tech_.on('timeupdate', timeupdateHandler);
8153              }, true);
8154            } else {
8155              this.tech_.off('timeupdate', timeupdateHandler);
8156            }
8157            /**
8158             * An event that fires when mode changes on this track. This allows
8159             * the TextTrackList that holds this track to act accordingly.
8160             *
8161             * > Note: This is not part of the spec!
8162             *
8163             * @event TextTrack#modechange
8164             * @type {EventTarget~Event}
8165             */
8166
8167
8168            this.trigger('modechange');
8169          }
8170        },
8171
8172        /**
8173         * @memberof TextTrack
8174         * @member {TextTrackCueList} cues
8175         *         The text track cue list for this TextTrack.
8176         * @instance
8177         */
8178        cues: {
8179          get: function get() {
8180            if (!this.loaded_) {
8181              return null;
8182            }
8183
8184            return cues;
8185          },
8186          set: function set() {}
8187        },
8188
8189        /**
8190         * @memberof TextTrack
8191         * @member {TextTrackCueList} activeCues
8192         *         The list text track cues that are currently active for this TextTrack.
8193         * @instance
8194         */
8195        activeCues: {
8196          get: function get() {
8197            if (!this.loaded_) {
8198              return null;
8199            } // nothing to do
8200
8201
8202            if (this.cues.length === 0) {
8203              return activeCues;
8204            }
8205
8206            var ct = this.tech_.currentTime();
8207            var active = [];
8208
8209            for (var i = 0, l = this.cues.length; i < l; i++) {
8210              var cue = this.cues[i];
8211
8212              if (cue.startTime <= ct && cue.endTime >= ct) {
8213                active.push(cue);
8214              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
8215                active.push(cue);
8216              }
8217            }
8218
8219            changed = false;
8220
8221            if (active.length !== this.activeCues_.length) {
8222              changed = true;
8223            } else {
8224              for (var _i = 0; _i < active.length; _i++) {
8225                if (this.activeCues_.indexOf(active[_i]) === -1) {
8226                  changed = true;
8227                }
8228              }
8229            }
8230
8231            this.activeCues_ = active;
8232            activeCues.setCues_(this.activeCues_);
8233            return activeCues;
8234          },
8235          // /!\ Keep this setter empty (see the timeupdate handler above)
8236          set: function set() {}
8237        }
8238      });
8239
8240      if (settings.src) {
8241        _this.src = settings.src;
8242
8243        if (!_this.preload_) {
8244          // Tracks will load on-demand.
8245          // Act like we're loaded for other purposes.
8246          _this.loaded_ = true;
8247        }
8248
8249        if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
8250          loadTrack(_this.src, assertThisInitialized(_this));
8251        }
8252      } else {
8253        _this.loaded_ = true;
8254      }
8255
8256      return _this;
8257    }
8258    /**
8259     * Add a cue to the internal list of cues.
8260     *
8261     * @param {TextTrack~Cue} cue
8262     *        The cue to add to our internal list
8263     */
8264
8265
8266    var _proto = TextTrack.prototype;
8267
8268    _proto.addCue = function addCue(originalCue) {
8269      var cue = originalCue;
8270
8271      if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
8272        cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8273
8274        for (var prop in originalCue) {
8275          if (!(prop in cue)) {
8276            cue[prop] = originalCue[prop];
8277          }
8278        } // make sure that `id` is copied over
8279
8280
8281        cue.id = originalCue.id;
8282        cue.originalCue_ = originalCue;
8283      }
8284
8285      var tracks = this.tech_.textTracks();
8286
8287      for (var i = 0; i < tracks.length; i++) {
8288        if (tracks[i] !== this) {
8289          tracks[i].removeCue(cue);
8290        }
8291      }
8292
8293      this.cues_.push(cue);
8294      this.cues.setCues_(this.cues_);
8295    }
8296    /**
8297     * Remove a cue from our internal list
8298     *
8299     * @param {TextTrack~Cue} removeCue
8300     *        The cue to remove from our internal list
8301     */
8302    ;
8303
8304    _proto.removeCue = function removeCue(_removeCue) {
8305      var i = this.cues_.length;
8306
8307      while (i--) {
8308        var cue = this.cues_[i];
8309
8310        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
8311          this.cues_.splice(i, 1);
8312          this.cues.setCues_(this.cues_);
8313          break;
8314        }
8315      }
8316    };
8317
8318    return TextTrack;
8319  }(Track);
8320  /**
8321   * cuechange - One or more cues in the track have become active or stopped being active.
8322   */
8323
8324
8325  TextTrack.prototype.allowedEvents_ = {
8326    cuechange: 'cuechange'
8327  };
8328
8329  /**
8330   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8331   * only one `AudioTrack` in the list will be enabled at a time.
8332   *
8333   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8334   * @extends Track
8335   */
8336
8337  var AudioTrack = /*#__PURE__*/function (_Track) {
8338    inheritsLoose(AudioTrack, _Track);
8339
8340    /**
8341     * Create an instance of this class.
8342     *
8343     * @param {Object} [options={}]
8344     *        Object of option names and values
8345     *
8346     * @param {AudioTrack~Kind} [options.kind='']
8347     *        A valid audio track kind
8348     *
8349     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8350     *        A unique id for this AudioTrack.
8351     *
8352     * @param {string} [options.label='']
8353     *        The menu label for this track.
8354     *
8355     * @param {string} [options.language='']
8356     *        A valid two character language code.
8357     *
8358     * @param {boolean} [options.enabled]
8359     *        If this track is the one that is currently playing. If this track is part of
8360     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8361     */
8362    function AudioTrack(options) {
8363      var _this;
8364
8365      if (options === void 0) {
8366        options = {};
8367      }
8368
8369      var settings = mergeOptions(options, {
8370        kind: AudioTrackKind[options.kind] || ''
8371      });
8372      _this = _Track.call(this, settings) || this;
8373      var enabled = false;
8374      /**
8375       * @memberof AudioTrack
8376       * @member {boolean} enabled
8377       *         If this `AudioTrack` is enabled or not. When setting this will
8378       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8379       * @instance
8380       *
8381       * @fires VideoTrack#selectedchange
8382       */
8383
8384      Object.defineProperty(assertThisInitialized(_this), 'enabled', {
8385        get: function get() {
8386          return enabled;
8387        },
8388        set: function set(newEnabled) {
8389          // an invalid or unchanged value
8390          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8391            return;
8392          }
8393
8394          enabled = newEnabled;
8395          /**
8396           * An event that fires when enabled changes on this track. This allows
8397           * the AudioTrackList that holds this track to act accordingly.
8398           *
8399           * > Note: This is not part of the spec! Native tracks will do
8400           *         this internally without an event.
8401           *
8402           * @event AudioTrack#enabledchange
8403           * @type {EventTarget~Event}
8404           */
8405
8406          this.trigger('enabledchange');
8407        }
8408      }); // if the user sets this track to selected then
8409      // set selected to that true value otherwise
8410      // we keep it false
8411
8412      if (settings.enabled) {
8413        _this.enabled = settings.enabled;
8414      }
8415
8416      _this.loaded_ = true;
8417      return _this;
8418    }
8419
8420    return AudioTrack;
8421  }(Track);
8422
8423  /**
8424   * A representation of a single `VideoTrack`.
8425   *
8426   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8427   * @extends Track
8428   */
8429
8430  var VideoTrack = /*#__PURE__*/function (_Track) {
8431    inheritsLoose(VideoTrack, _Track);
8432
8433    /**
8434     * Create an instance of this class.
8435     *
8436     * @param {Object} [options={}]
8437     *        Object of option names and values
8438     *
8439     * @param {string} [options.kind='']
8440     *        A valid {@link VideoTrack~Kind}
8441     *
8442     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8443     *        A unique id for this AudioTrack.
8444     *
8445     * @param {string} [options.label='']
8446     *        The menu label for this track.
8447     *
8448     * @param {string} [options.language='']
8449     *        A valid two character language code.
8450     *
8451     * @param {boolean} [options.selected]
8452     *        If this track is the one that is currently playing.
8453     */
8454    function VideoTrack(options) {
8455      var _this;
8456
8457      if (options === void 0) {
8458        options = {};
8459      }
8460
8461      var settings = mergeOptions(options, {
8462        kind: VideoTrackKind[options.kind] || ''
8463      });
8464      _this = _Track.call(this, settings) || this;
8465      var selected = false;
8466      /**
8467       * @memberof VideoTrack
8468       * @member {boolean} selected
8469       *         If this `VideoTrack` is selected or not. When setting this will
8470       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8471       * @instance
8472       *
8473       * @fires VideoTrack#selectedchange
8474       */
8475
8476      Object.defineProperty(assertThisInitialized(_this), 'selected', {
8477        get: function get() {
8478          return selected;
8479        },
8480        set: function set(newSelected) {
8481          // an invalid or unchanged value
8482          if (typeof newSelected !== 'boolean' || newSelected === selected) {
8483            return;
8484          }
8485
8486          selected = newSelected;
8487          /**
8488           * An event that fires when selected changes on this track. This allows
8489           * the VideoTrackList that holds this track to act accordingly.
8490           *
8491           * > Note: This is not part of the spec! Native tracks will do
8492           *         this internally without an event.
8493           *
8494           * @event VideoTrack#selectedchange
8495           * @type {EventTarget~Event}
8496           */
8497
8498          this.trigger('selectedchange');
8499        }
8500      }); // if the user sets this track to selected then
8501      // set selected to that true value otherwise
8502      // we keep it false
8503
8504      if (settings.selected) {
8505        _this.selected = settings.selected;
8506      }
8507
8508      return _this;
8509    }
8510
8511    return VideoTrack;
8512  }(Track);
8513
8514  /**
8515   * @memberof HTMLTrackElement
8516   * @typedef {HTMLTrackElement~ReadyState}
8517   * @enum {number}
8518   */
8519
8520  var NONE = 0;
8521  var LOADING = 1;
8522  var LOADED = 2;
8523  var ERROR = 3;
8524  /**
8525   * A single track represented in the DOM.
8526   *
8527   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8528   * @extends EventTarget
8529   */
8530
8531  var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) {
8532    inheritsLoose(HTMLTrackElement, _EventTarget);
8533
8534    /**
8535     * Create an instance of this class.
8536     *
8537     * @param {Object} options={}
8538     *        Object of option names and values
8539     *
8540     * @param {Tech} options.tech
8541     *        A reference to the tech that owns this HTMLTrackElement.
8542     *
8543     * @param {TextTrack~Kind} [options.kind='subtitles']
8544     *        A valid text track kind.
8545     *
8546     * @param {TextTrack~Mode} [options.mode='disabled']
8547     *        A valid text track mode.
8548     *
8549     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8550     *        A unique id for this TextTrack.
8551     *
8552     * @param {string} [options.label='']
8553     *        The menu label for this track.
8554     *
8555     * @param {string} [options.language='']
8556     *        A valid two character language code.
8557     *
8558     * @param {string} [options.srclang='']
8559     *        A valid two character language code. An alternative, but deprioritized
8560     *        vesion of `options.language`
8561     *
8562     * @param {string} [options.src]
8563     *        A url to TextTrack cues.
8564     *
8565     * @param {boolean} [options.default]
8566     *        If this track should default to on or off.
8567     */
8568    function HTMLTrackElement(options) {
8569      var _this;
8570
8571      if (options === void 0) {
8572        options = {};
8573      }
8574
8575      _this = _EventTarget.call(this) || this;
8576      var readyState;
8577      var track = new TextTrack(options);
8578      _this.kind = track.kind;
8579      _this.src = track.src;
8580      _this.srclang = track.language;
8581      _this.label = track.label;
8582      _this["default"] = track["default"];
8583      Object.defineProperties(assertThisInitialized(_this), {
8584        /**
8585         * @memberof HTMLTrackElement
8586         * @member {HTMLTrackElement~ReadyState} readyState
8587         *         The current ready state of the track element.
8588         * @instance
8589         */
8590        readyState: {
8591          get: function get() {
8592            return readyState;
8593          }
8594        },
8595
8596        /**
8597         * @memberof HTMLTrackElement
8598         * @member {TextTrack} track
8599         *         The underlying TextTrack object.
8600         * @instance
8601         *
8602         */
8603        track: {
8604          get: function get() {
8605            return track;
8606          }
8607        }
8608      });
8609      readyState = NONE;
8610      /**
8611       * @listens TextTrack#loadeddata
8612       * @fires HTMLTrackElement#load
8613       */
8614
8615      track.addEventListener('loadeddata', function () {
8616        readyState = LOADED;
8617
8618        _this.trigger({
8619          type: 'load',
8620          target: assertThisInitialized(_this)
8621        });
8622      });
8623      return _this;
8624    }
8625
8626    return HTMLTrackElement;
8627  }(EventTarget);
8628
8629  HTMLTrackElement.prototype.allowedEvents_ = {
8630    load: 'load'
8631  };
8632  HTMLTrackElement.NONE = NONE;
8633  HTMLTrackElement.LOADING = LOADING;
8634  HTMLTrackElement.LOADED = LOADED;
8635  HTMLTrackElement.ERROR = ERROR;
8636
8637  /*
8638   * This file contains all track properties that are used in
8639   * player.js, tech.js, html5.js and possibly other techs in the future.
8640   */
8641
8642  var NORMAL = {
8643    audio: {
8644      ListClass: AudioTrackList,
8645      TrackClass: AudioTrack,
8646      capitalName: 'Audio'
8647    },
8648    video: {
8649      ListClass: VideoTrackList,
8650      TrackClass: VideoTrack,
8651      capitalName: 'Video'
8652    },
8653    text: {
8654      ListClass: TextTrackList,
8655      TrackClass: TextTrack,
8656      capitalName: 'Text'
8657    }
8658  };
8659  Object.keys(NORMAL).forEach(function (type) {
8660    NORMAL[type].getterName = type + "Tracks";
8661    NORMAL[type].privateName = type + "Tracks_";
8662  });
8663  var REMOTE = {
8664    remoteText: {
8665      ListClass: TextTrackList,
8666      TrackClass: TextTrack,
8667      capitalName: 'RemoteText',
8668      getterName: 'remoteTextTracks',
8669      privateName: 'remoteTextTracks_'
8670    },
8671    remoteTextEl: {
8672      ListClass: HtmlTrackElementList,
8673      TrackClass: HTMLTrackElement,
8674      capitalName: 'RemoteTextTrackEls',
8675      getterName: 'remoteTextTrackEls',
8676      privateName: 'remoteTextTrackEls_'
8677    }
8678  };
8679
8680  var ALL = _extends_1({}, NORMAL, REMOTE);
8681
8682  REMOTE.names = Object.keys(REMOTE);
8683  NORMAL.names = Object.keys(NORMAL);
8684  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8685
8686  /**
8687   * Copyright 2013 vtt.js Contributors
8688   *
8689   * Licensed under the Apache License, Version 2.0 (the "License");
8690   * you may not use this file except in compliance with the License.
8691   * You may obtain a copy of the License at
8692   *
8693   *   http://www.apache.org/licenses/LICENSE-2.0
8694   *
8695   * Unless required by applicable law or agreed to in writing, software
8696   * distributed under the License is distributed on an "AS IS" BASIS,
8697   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8698   * See the License for the specific language governing permissions and
8699   * limitations under the License.
8700   */
8701
8702  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
8703
8704  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
8705
8706  var _objCreate = Object.create || function () {
8707    function F() {}
8708
8709    return function (o) {
8710      if (arguments.length !== 1) {
8711        throw new Error('Object.create shim only accepts one parameter.');
8712      }
8713
8714      F.prototype = o;
8715      return new F();
8716    };
8717  }(); // Creates a new ParserError object from an errorData object. The errorData
8718  // object should have default code and message properties. The default message
8719  // property can be overriden by passing in a message parameter.
8720  // See ParsingError.Errors below for acceptable errors.
8721
8722
8723  function ParsingError(errorData, message) {
8724    this.name = "ParsingError";
8725    this.code = errorData.code;
8726    this.message = message || errorData.message;
8727  }
8728
8729  ParsingError.prototype = _objCreate(Error.prototype);
8730  ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
8731
8732  ParsingError.Errors = {
8733    BadSignature: {
8734      code: 0,
8735      message: "Malformed WebVTT signature."
8736    },
8737    BadTimeStamp: {
8738      code: 1,
8739      message: "Malformed time stamp."
8740    }
8741  }; // Try to parse input as a time stamp.
8742
8743  function parseTimeStamp(input) {
8744    function computeSeconds(h, m, s, f) {
8745      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8746    }
8747
8748    var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
8749
8750    if (!m) {
8751      return null;
8752    }
8753
8754    if (m[3]) {
8755      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8756      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8757    } else if (m[1] > 59) {
8758      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8759      // First position is hours as it's over 59.
8760      return computeSeconds(m[1], m[2], 0, m[4]);
8761    } else {
8762      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8763      return computeSeconds(0, m[1], m[2], m[4]);
8764    }
8765  } // A settings object holds key/value pairs and will ignore anything but the first
8766  // assignment to a specific key.
8767
8768
8769  function Settings() {
8770    this.values = _objCreate(null);
8771  }
8772
8773  Settings.prototype = {
8774    // Only accept the first assignment to any key.
8775    set: function set(k, v) {
8776      if (!this.get(k) && v !== "") {
8777        this.values[k] = v;
8778      }
8779    },
8780    // Return the value for a key, or a default value.
8781    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8782    // a number of possible default values as properties where 'defaultKey' is
8783    // the key of the property that will be chosen; otherwise it's assumed to be
8784    // a single value.
8785    get: function get(k, dflt, defaultKey) {
8786      if (defaultKey) {
8787        return this.has(k) ? this.values[k] : dflt[defaultKey];
8788      }
8789
8790      return this.has(k) ? this.values[k] : dflt;
8791    },
8792    // Check whether we have a value for a key.
8793    has: function has(k) {
8794      return k in this.values;
8795    },
8796    // Accept a setting if its one of the given alternatives.
8797    alt: function alt(k, v, a) {
8798      for (var n = 0; n < a.length; ++n) {
8799        if (v === a[n]) {
8800          this.set(k, v);
8801          break;
8802        }
8803      }
8804    },
8805    // Accept a setting if its a valid (signed) integer.
8806    integer: function integer(k, v) {
8807      if (/^-?\d+$/.test(v)) {
8808        // integer
8809        this.set(k, parseInt(v, 10));
8810      }
8811    },
8812    // Accept a setting if its a valid percentage.
8813    percent: function percent(k, v) {
8814      var m;
8815
8816      if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8817        v = parseFloat(v);
8818
8819        if (v >= 0 && v <= 100) {
8820          this.set(k, v);
8821          return true;
8822        }
8823      }
8824
8825      return false;
vendor: 5,448 bytes, lines 8826-9027
8826    }
8827  }; // Helper function to parse input into groups separated by 'groupDelim', and
8828  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8829
8830  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8831    var groups = groupDelim ? input.split(groupDelim) : [input];
8832
8833    for (var i in groups) {
8834      if (typeof groups[i] !== "string") {
8835        continue;
8836      }
8837
8838      var kv = groups[i].split(keyValueDelim);
8839
8840      if (kv.length !== 2) {
8841        continue;
8842      }
8843
8844      var k = kv[0];
8845      var v = kv[1];
8846      callback(k, v);
8847    }
8848  }
8849
8850  function parseCue(input, cue, regionList) {
8851    // Remember the original input if we need to throw an error.
8852    var oInput = input; // 4.1 WebVTT timestamp
8853
8854    function consumeTimeStamp() {
8855      var ts = parseTimeStamp(input);
8856
8857      if (ts === null) {
8858        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8859      } // Remove time stamp from input.
8860
8861
8862      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8863      return ts;
8864    } // 4.4.2 WebVTT cue settings
8865
8866
8867    function consumeCueSettings(input, cue) {
8868      var settings = new Settings();
8869      parseOptions(input, function (k, v) {
8870        switch (k) {
8871          case "region":
8872            // Find the last region we parsed with the same region id.
8873            for (var i = regionList.length - 1; i >= 0; i--) {
8874              if (regionList[i].id === v) {
8875                settings.set(k, regionList[i].region);
8876                break;
8877              }
8878            }
8879
8880            break;
8881
8882          case "vertical":
8883            settings.alt(k, v, ["rl", "lr"]);
8884            break;
8885
8886          case "line":
8887            var vals = v.split(","),
8888                vals0 = vals[0];
8889            settings.integer(k, vals0);
8890            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8891            settings.alt(k, vals0, ["auto"]);
8892
8893            if (vals.length === 2) {
8894              settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
8895            }
8896
8897            break;
8898
8899          case "position":
8900            vals = v.split(",");
8901            settings.percent(k, vals[0]);
8902
8903            if (vals.length === 2) {
8904              settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
8905            }
8906
8907            break;
8908
8909          case "size":
8910            settings.percent(k, v);
8911            break;
8912
8913          case "align":
8914            settings.alt(k, v, ["start", "center", "end", "left", "right"]);
8915            break;
8916        }
8917      }, /:/, /\s/); // Apply default values for any missing fields.
8918
8919      cue.region = settings.get("region", null);
8920      cue.vertical = settings.get("vertical", "");
8921
8922      try {
8923        cue.line = settings.get("line", "auto");
8924      } catch (e) {}
8925
8926      cue.lineAlign = settings.get("lineAlign", "start");
8927      cue.snapToLines = settings.get("snapToLines", true);
8928      cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center
8929
8930      try {
8931        cue.align = settings.get("align", "center");
8932      } catch (e) {
8933        cue.align = settings.get("align", "middle");
8934      }
8935
8936      try {
8937        cue.position = settings.get("position", "auto");
8938      } catch (e) {
8939        cue.position = settings.get("position", {
8940          start: 0,
8941          left: 0,
8942          center: 50,
8943          middle: 50,
8944          end: 100,
8945          right: 100
8946        }, cue.align);
8947      }
8948
8949      cue.positionAlign = settings.get("positionAlign", {
8950        start: "start",
8951        left: "start",
8952        center: "center",
8953        middle: "center",
8954        end: "end",
8955        right: "end"
8956      }, cue.align);
8957    }
8958
8959    function skipWhitespace() {
8960      input = input.replace(/^\s+/, "");
8961    } // 4.1 WebVTT cue timings.
8962
8963
8964    skipWhitespace();
8965    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8966
8967    skipWhitespace();
8968
8969    if (input.substr(0, 3) !== "-->") {
8970      // (3) next characters must match "-->"
8971      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8972    }
8973
8974    input = input.substr(3);
8975    skipWhitespace();
8976    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8977    // 4.1 WebVTT cue settings list.
8978
8979    skipWhitespace();
8980    consumeCueSettings(input, cue);
8981  }
8982
8983  var TEXTAREA_ELEMENT = document.createElement("textarea");
8984  var TAG_NAME = {
8985    c: "span",
8986    i: "i",
8987    b: "b",
8988    u: "u",
8989    ruby: "ruby",
8990    rt: "rt",
8991    v: "span",
8992    lang: "span"
8993  }; // 5.1 default text color
8994  // 5.2 default text background color is equivalent to text color with bg_ prefix
8995
8996  var DEFAULT_COLOR_CLASS = {
8997    white: 'rgba(255,255,255,1)',
8998    lime: 'rgba(0,255,0,1)',
8999    cyan: 'rgba(0,255,255,1)',
9000    red: 'rgba(255,0,0,1)',
9001    yellow: 'rgba(255,255,0,1)',
9002    magenta: 'rgba(255,0,255,1)',
9003    blue: 'rgba(0,0,255,1)',
9004    black: 'rgba(0,0,0,1)'
9005  };
9006  var TAG_ANNOTATION = {
9007    v: "title",
9008    lang: "lang"
9009  };
9010  var NEEDS_PARENT = {
9011    rt: "ruby"
9012  }; // Parse content into a document fragment.
9013
9014  function parseContent(window, input) {
9015    function nextToken() {
9016      // Check for end-of-string.
9017      if (!input) {
9018        return null;
9019      } // Consume 'n' characters from the input.
9020
9021
9022      function consume(result) {
9023        input = input.substr(result.length);
9024        return result;
9025      }
9026
9027      var m = input.match(/^([^<]*)(<[^>
vendor: 23,198 bytes, lines 9027-9687
9027]*>?)?/); // If there is some text before the next tag, return it, otherwise return
9028      // the tag.
9029
9030      return consume(m[1] ? m[1] : m[2]);
9031    }
9032
9033    function unescape(s) {
9034      TEXTAREA_ELEMENT.innerHTML = s;
9035      s = TEXTAREA_ELEMENT.textContent;
9036      TEXTAREA_ELEMENT.textContent = "";
9037      return s;
9038    }
9039
9040    function shouldAdd(current, element) {
9041      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
9042    } // Create an element for this tag.
9043
9044
9045    function createElement(type, annotation) {
9046      var tagName = TAG_NAME[type];
9047
9048      if (!tagName) {
9049        return null;
9050      }
9051
9052      var element = window.document.createElement(tagName);
9053      var name = TAG_ANNOTATION[type];
9054
9055      if (name && annotation) {
9056        element[name] = annotation.trim();
9057      }
9058
9059      return element;
9060    }
9061
9062    var rootDiv = window.document.createElement("div"),
9063        current = rootDiv,
9064        t,
9065        tagStack = [];
9066
9067    while ((t = nextToken()) !== null) {
9068      if (t[0] === '<') {
9069        if (t[1] === "/") {
9070          // If the closing tag matches, move back up to the parent node.
9071          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
9072            tagStack.pop();
9073            current = current.parentNode;
9074          } // Otherwise just ignore the end tag.
9075
9076
9077          continue;
9078        }
9079
9080        var ts = parseTimeStamp(t.substr(1, t.length - 2));
9081        var node;
9082
9083        if (ts) {
9084          // Timestamps are lead nodes as well.
9085          node = window.document.createProcessingInstruction("timestamp", ts);
9086          current.appendChild(node);
9087          continue;
9088        }
9089
9090        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
9091
9092        if (!m) {
9093          continue;
9094        } // Try to construct an element, and ignore the tag if we couldn't.
9095
9096
9097        node = createElement(m[1], m[3]);
9098
9099        if (!node) {
9100          continue;
9101        } // Determine if the tag should be added based on the context of where it
9102        // is placed in the cuetext.
9103
9104
9105        if (!shouldAdd(current, node)) {
9106          continue;
9107        } // Set the class list (as a list of classes, separated by space).
9108
9109
9110        if (m[2]) {
9111          var classes = m[2].split('.');
9112          classes.forEach(function (cl) {
9113            var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color
9114
9115            var colorName = bgColor ? cl.slice(3) : cl;
9116
9117            if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
9118              var propName = bgColor ? 'background-color' : 'color';
9119              var propValue = DEFAULT_COLOR_CLASS[colorName];
9120              node.style[propName] = propValue;
9121            }
9122          });
9123          node.className = classes.join(' ');
9124        } // Append the node to the current node, and enter the scope of the new
9125        // node.
9126
9127
9128        tagStack.push(m[1]);
9129        current.appendChild(node);
9130        current = node;
9131        continue;
9132      } // Text nodes are leaf nodes.
9133
9134
9135      current.appendChild(window.document.createTextNode(unescape(t)));
9136    }
9137
9138    return rootDiv;
9139  } // This is a list of all the Unicode characters that have a strong
9140  // right-to-left category. What this means is that these characters are
9141  // written right-to-left for sure. It was generated by pulling all the strong
9142  // right-to-left characters out of the Unicode data table. That table can
9143  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
9144
9145
9146  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, 0x1093f], [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]];
9147
9148  function isStrongRTLChar(charCode) {
9149    for (var i = 0; i < strongRTLRanges.length; i++) {
9150      var currentRange = strongRTLRanges[i];
9151
9152      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
9153        return true;
9154      }
9155    }
9156
9157    return false;
9158  }
9159
9160  function determineBidi(cueDiv) {
9161    var nodeStack = [],
9162        text = "",
9163        charCode;
9164
9165    if (!cueDiv || !cueDiv.childNodes) {
9166      return "ltr";
9167    }
9168
9169    function pushNodes(nodeStack, node) {
9170      for (var i = node.childNodes.length - 1; i >= 0; i--) {
9171        nodeStack.push(node.childNodes[i]);
9172      }
9173    }
9174
9175    function nextTextNode(nodeStack) {
9176      if (!nodeStack || !nodeStack.length) {
9177        return null;
9178      }
9179
9180      var node = nodeStack.pop(),
9181          text = node.textContent || node.innerText;
9182
9183      if (text) {
9184        // TODO: This should match all unicode type B characters (paragraph
9185        // separator characters). See issue #115.
9186        var m = text.match(/^.*(\n|\r)/);
9187
9188        if (m) {
9189          nodeStack.length = 0;
9190          return m[0];
9191        }
9192
9193        return text;
9194      }
9195
9196      if (node.tagName === "ruby") {
9197        return nextTextNode(nodeStack);
9198      }
9199
9200      if (node.childNodes) {
9201        pushNodes(nodeStack, node);
9202        return nextTextNode(nodeStack);
9203      }
9204    }
9205
9206    pushNodes(nodeStack, cueDiv);
9207
9208    while (text = nextTextNode(nodeStack)) {
9209      for (var i = 0; i < text.length; i++) {
9210        charCode = text.charCodeAt(i);
9211
9212        if (isStrongRTLChar(charCode)) {
9213          return "rtl";
9214        }
9215      }
9216    }
9217
9218    return "ltr";
9219  }
9220
9221  function computeLinePos(cue) {
9222    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
9223      return cue.line;
9224    }
9225
9226    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
9227      return -1;
9228    }
9229
9230    var track = cue.track,
9231        trackList = track.textTrackList,
9232        count = 0;
9233
9234    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
9235      if (trackList[i].mode === "showing") {
9236        count++;
9237      }
9238    }
9239
9240    return ++count * -1;
9241  }
9242
9243  function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
9244  // div on 'this'.
9245
9246
9247  StyleBox.prototype.applyStyles = function (styles, div) {
9248    div = div || this.div;
9249
9250    for (var prop in styles) {
9251      if (styles.hasOwnProperty(prop)) {
9252        div.style[prop] = styles[prop];
9253      }
9254    }
9255  };
9256
9257  StyleBox.prototype.formatStyle = function (val, unit) {
9258    return val === 0 ? 0 : val + unit;
9259  }; // Constructs the computed display state of the cue (a div). Places the div
9260  // into the overlay which should be a block level element (usually a div).
9261
9262
9263  function CueStyleBox(window, cue, styleOptions) {
9264    StyleBox.call(this);
9265    this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
9266    // have inline positioning and will function as the cue background box.
9267
9268    this.cueDiv = parseContent(window, cue.text);
9269    var styles = {
9270      color: "rgba(255, 255, 255, 1)",
9271      backgroundColor: "rgba(0, 0, 0, 0.8)",
9272      position: "relative",
9273      left: 0,
9274      right: 0,
9275      top: 0,
9276      bottom: 0,
9277      display: "inline",
9278      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9279      unicodeBidi: "plaintext"
9280    };
9281    this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
9282    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
9283    // mirrors of them except middle instead of center on Safari.
9284
9285    this.div = window.document.createElement("div");
9286    styles = {
9287      direction: determineBidi(this.cueDiv),
9288      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9289      unicodeBidi: "plaintext",
9290      textAlign: cue.align === "middle" ? "center" : cue.align,
9291      font: styleOptions.font,
9292      whiteSpace: "pre-line",
9293      position: "absolute"
9294    };
9295    this.applyStyles(styles);
9296    this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
9297    // position of the cue box. The reference edge will be resolved later when
9298    // the box orientation styles are applied.
9299
9300    var textPos = 0;
9301
9302    switch (cue.positionAlign) {
9303      case "start":
9304        textPos = cue.position;
9305        break;
9306
9307      case "center":
9308        textPos = cue.position - cue.size / 2;
9309        break;
9310
9311      case "end":
9312        textPos = cue.position - cue.size;
9313        break;
9314    } // Horizontal box orientation; textPos is the distance from the left edge of the
9315    // area to the left edge of the box and cue.size is the distance extending to
9316    // the right from there.
9317
9318
9319    if (cue.vertical === "") {
9320      this.applyStyles({
9321        left: this.formatStyle(textPos, "%"),
9322        width: this.formatStyle(cue.size, "%")
9323      }); // Vertical box orientation; textPos is the distance from the top edge of the
9324      // area to the top edge of the box and cue.size is the height extending
9325      // downwards from there.
9326    } else {
9327      this.applyStyles({
9328        top: this.formatStyle(textPos, "%"),
9329        height: this.formatStyle(cue.size, "%")
9330      });
9331    }
9332
9333    this.move = function (box) {
9334      this.applyStyles({
9335        top: this.formatStyle(box.top, "px"),
9336        bottom: this.formatStyle(box.bottom, "px"),
9337        left: this.formatStyle(box.left, "px"),
9338        right: this.formatStyle(box.right, "px"),
9339        height: this.formatStyle(box.height, "px"),
9340        width: this.formatStyle(box.width, "px")
9341      });
9342    };
9343  }
9344
9345  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9346  CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
9347  // compute things with such as if it overlaps or intersects with another Element.
9348  // Can initialize it with either a StyleBox or another BoxPosition.
9349
9350  function BoxPosition(obj) {
9351    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9352    // was passed in and we need to copy the results of 'getBoundingClientRect'
9353    // as the object returned is readonly. All co-ordinate values are in reference
9354    // to the viewport origin (top left).
9355    var lh, height, width, top;
9356
9357    if (obj.div) {
9358      height = obj.div.offsetHeight;
9359      width = obj.div.offsetWidth;
9360      top = obj.div.offsetTop;
9361      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9362      obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
9363      // the inner div's lines. This could be due to bold text, etc, on some platforms.
9364      // In this case we should get the average line height and use that. This will
9365      // result in the desired behaviour.
9366
9367      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9368    }
9369
9370    this.left = obj.left;
9371    this.right = obj.right;
9372    this.top = obj.top || top;
9373    this.height = obj.height || height;
9374    this.bottom = obj.bottom || top + (obj.height || height);
9375    this.width = obj.width || width;
9376    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9377  } // Move the box along a particular axis. Optionally pass in an amount to move
9378  // the box. If no amount is passed then the default is the line height of the
9379  // box.
9380
9381
9382  BoxPosition.prototype.move = function (axis, toMove) {
9383    toMove = toMove !== undefined ? toMove : this.lineHeight;
9384
9385    switch (axis) {
9386      case "+x":
9387        this.left += toMove;
9388        this.right += toMove;
9389        break;
9390
9391      case "-x":
9392        this.left -= toMove;
9393        this.right -= toMove;
9394        break;
9395
9396      case "+y":
9397        this.top += toMove;
9398        this.bottom += toMove;
9399        break;
9400
9401      case "-y":
9402        this.top -= toMove;
9403        this.bottom -= toMove;
9404        break;
9405    }
9406  }; // Check if this box overlaps another box, b2.
9407
9408
9409  BoxPosition.prototype.overlaps = function (b2) {
9410    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9411  }; // Check if this box overlaps any other boxes in boxes.
9412
9413
9414  BoxPosition.prototype.overlapsAny = function (boxes) {
9415    for (var i = 0; i < boxes.length; i++) {
9416      if (this.overlaps(boxes[i])) {
9417        return true;
9418      }
9419    }
9420
9421    return false;
9422  }; // Check if this box is within another box.
9423
9424
9425  BoxPosition.prototype.within = function (container) {
9426    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9427  }; // Check if this box is entirely within the container or it is overlapping
9428  // on the edge opposite of the axis direction passed. For example, if "+x" is
9429  // passed and the box is overlapping on the left edge of the container, then
9430  // return true.
9431
9432
9433  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9434    switch (axis) {
9435      case "+x":
9436        return this.left < container.left;
9437
9438      case "-x":
9439        return this.right > container.right;
9440
9441      case "+y":
9442        return this.top < container.top;
9443
9444      case "-y":
9445        return this.bottom > container.bottom;
9446    }
9447  }; // Find the percentage of the area that this box is overlapping with another
9448  // box.
9449
9450
9451  BoxPosition.prototype.intersectPercentage = function (b2) {
9452    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9453        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9454        intersectArea = x * y;
9455    return intersectArea / (this.height * this.width);
9456  }; // Convert the positions from this box to CSS compatible positions using
9457  // the reference container's positions. This has to be done because this
9458  // box's positions are in reference to the viewport origin, whereas, CSS
9459  // values are in referecne to their respective edges.
9460
9461
9462  BoxPosition.prototype.toCSSCompatValues = function (reference) {
9463    return {
9464      top: this.top - reference.top,
9465      bottom: reference.bottom - this.bottom,
9466      left: this.left - reference.left,
9467      right: reference.right - this.right,
9468      height: this.height,
9469      width: this.width
9470    };
9471  }; // Get an object that represents the box's position without anything extra.
9472  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9473
9474
9475  BoxPosition.getSimpleBoxPosition = function (obj) {
9476    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9477    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9478    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9479    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9480    var ret = {
9481      left: obj.left,
9482      right: obj.right,
9483      top: obj.top || top,
9484      height: obj.height || height,
9485      bottom: obj.bottom || top + (obj.height || height),
9486      width: obj.width || width
9487    };
9488    return ret;
9489  }; // Move a StyleBox to its specified, or next best, position. The containerBox
9490  // is the box that contains the StyleBox, such as a div. boxPositions are
9491  // a list of other boxes that the styleBox can't overlap with.
9492
9493
9494  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9495    // Find the best position for a cue box, b, on the video. The axis parameter
9496    // is a list of axis, the order of which, it will move the box along. For example:
9497    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9498    // direction. If it doesn't find a good position for it there it will then move
9499    // it along the x axis in the negative direction.
9500    function findBestPosition(b, axis) {
9501      var bestPosition,
9502          specifiedPosition = new BoxPosition(b),
9503          percentage = 1; // Highest possible so the first thing we get is better.
9504
9505      for (var i = 0; i < axis.length; i++) {
9506        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9507          b.move(axis[i]);
9508        } // We found a spot where we aren't overlapping anything. This is our
9509        // best position.
9510
9511
9512        if (b.within(containerBox)) {
9513          return b;
9514        }
9515
9516        var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
9517        // then remember this position as the best position.
9518
9519        if (percentage > p) {
9520          bestPosition = new BoxPosition(b);
9521          percentage = p;
9522        } // Reset the box position to the specified position.
9523
9524
9525        b = new BoxPosition(specifiedPosition);
9526      }
9527
9528      return bestPosition || specifiedPosition;
9529    }
9530
9531    var boxPosition = new BoxPosition(styleBox),
9532        cue = styleBox.cue,
9533        linePos = computeLinePos(cue),
9534        axis = []; // If we have a line number to align the cue to.
9535
9536    if (cue.snapToLines) {
9537      var size;
9538
9539      switch (cue.vertical) {
9540        case "":
9541          axis = ["+y", "-y"];
9542          size = "height";
9543          break;
9544
9545        case "rl":
9546          axis = ["+x", "-x"];
9547          size = "width";
9548          break;
9549
9550        case "lr":
9551          axis = ["-x", "+x"];
9552          size = "width";
9553          break;
9554      }
9555
9556      var step = boxPosition.lineHeight,
9557          position = step * Math.round(linePos),
9558          maxPosition = containerBox[size] + step,
9559          initialAxis = axis[0]; // If the specified intial position is greater then the max position then
9560      // clamp the box to the amount of steps it would take for the box to
9561      // reach the max position.
9562
9563      if (Math.abs(position) > maxPosition) {
9564        position = position < 0 ? -1 : 1;
9565        position *= Math.ceil(maxPosition / step) * step;
9566      } // If computed line position returns negative then line numbers are
9567      // relative to the bottom of the video instead of the top. Therefore, we
9568      // need to increase our initial position by the length or width of the
9569      // video, depending on the writing direction, and reverse our axis directions.
9570
9571
9572      if (linePos < 0) {
9573        position += cue.vertical === "" ? containerBox.height : containerBox.width;
9574        axis = axis.reverse();
9575      } // Move the box to the specified position. This may not be its best
9576      // position.
9577
9578
9579      boxPosition.move(initialAxis, position);
9580    } else {
9581      // If we have a percentage line value for the cue.
9582      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9583
9584      switch (cue.lineAlign) {
9585        case "center":
9586          linePos -= calculatedPercentage / 2;
9587          break;
9588
9589        case "end":
9590          linePos -= calculatedPercentage;
9591          break;
9592      } // Apply initial line position to the cue box.
9593
9594
9595      switch (cue.vertical) {
9596        case "":
9597          styleBox.applyStyles({
9598            top: styleBox.formatStyle(linePos, "%")
9599          });
9600          break;
9601
9602        case "rl":
9603          styleBox.applyStyles({
9604            left: styleBox.formatStyle(linePos, "%")
9605          });
9606          break;
9607
9608        case "lr":
9609          styleBox.applyStyles({
9610            right: styleBox.formatStyle(linePos, "%")
9611          });
9612          break;
9613      }
9614
9615      axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
9616      // to it.
9617
9618      boxPosition = new BoxPosition(styleBox);
9619    }
9620
9621    var bestPosition = findBestPosition(boxPosition, axis);
9622    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9623  }
9624
9625  function WebVTT$1() {} // Nothing
9626  // Helper to allow strings to be decoded instead of the default binary utf8 data.
9627
9628
9629  WebVTT$1.StringDecoder = function () {
9630    return {
9631      decode: function decode(data) {
9632        if (!data) {
9633          return "";
9634        }
9635
9636        if (typeof data !== "string") {
9637          throw new Error("Error - expected string data.");
9638        }
9639
9640        return decodeURIComponent(encodeURIComponent(data));
9641      }
9642    };
9643  };
9644
9645  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9646    if (!window || !cuetext) {
9647      return null;
9648    }
9649
9650    return parseContent(window, cuetext);
9651  };
9652
9653  var FONT_SIZE_PERCENT = 0.05;
9654  var FONT_STYLE = "sans-serif";
9655  var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
9656  // @param overlay A block level element (usually a div) that the computed cues
9657  //                and regions will be placed into.
9658
9659  WebVTT$1.processCues = function (window, cues, overlay) {
9660    if (!window || !cues || !overlay) {
9661      return null;
9662    } // Remove all previous children.
9663
9664
9665    while (overlay.firstChild) {
9666      overlay.removeChild(overlay.firstChild);
9667    }
9668
9669    var paddedOverlay = window.document.createElement("div");
9670    paddedOverlay.style.position = "absolute";
9671    paddedOverlay.style.left = "0";
9672    paddedOverlay.style.right = "0";
9673    paddedOverlay.style.top = "0";
9674    paddedOverlay.style.bottom = "0";
9675    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
9676    overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
9677    // be the case if a cue's state has been changed since the last computation or
9678    // if it has not been computed yet.
9679
9680    function shouldCompute(cues) {
9681      for (var i = 0; i < cues.length; i++) {
9682        if (cues[i].hasBeenReset || !cues[i].displayState) {
9683          return true;
9684        }
9685      }
9686
9687      return false;
vendor: 6,356 bytes, lines 9688-9897
9688    } // We don't need to recompute the cues' display states. Just reuse them.
9689
9690
9691    if (!shouldCompute(cues)) {
9692      for (var i = 0; i < cues.length; i++) {
9693        paddedOverlay.appendChild(cues[i].displayState);
9694      }
9695
9696      return;
9697    }
9698
9699    var boxPositions = [],
9700        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
9701        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
9702    var styleOptions = {
9703      font: fontSize + "px " + FONT_STYLE
9704    };
9705
9706    (function () {
9707      var styleBox, cue;
9708
9709      for (var i = 0; i < cues.length; i++) {
9710        cue = cues[i]; // Compute the intial position and styles of the cue div.
9711
9712        styleBox = new CueStyleBox(window, cue, styleOptions);
9713        paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
9714
9715        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
9716        // if we don't have too.
9717
9718        cue.displayState = styleBox.div;
9719        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
9720      }
9721    })();
9722  };
9723
9724  WebVTT$1.Parser = function (window, vttjs, decoder) {
9725    if (!decoder) {
9726      decoder = vttjs;
9727      vttjs = {};
9728    }
9729
9730    if (!vttjs) {
9731      vttjs = {};
9732    }
9733
9734    this.window = window;
9735    this.vttjs = vttjs;
9736    this.state = "INITIAL";
9737    this.buffer = "";
9738    this.decoder = decoder || new TextDecoder("utf8");
9739    this.regionList = [];
9740  };
9741
9742  WebVTT$1.Parser.prototype = {
9743    // If the error is a ParsingError then report it to the consumer if
9744    // possible. If it's not a ParsingError then throw it like normal.
9745    reportOrThrowError: function reportOrThrowError(e) {
9746      if (e instanceof ParsingError) {
9747        this.onparsingerror && this.onparsingerror(e);
9748      } else {
9749        throw e;
9750      }
9751    },
9752    parse: function parse(data) {
9753      var self = this; // If there is no data then we won't decode it, but will just try to parse
9754      // whatever is in buffer already. This may occur in circumstances, for
9755      // example when flush() is called.
9756
9757      if (data) {
9758        // Try to decode the data that we received.
9759        self.buffer += self.decoder.decode(data, {
9760          stream: true
9761        });
9762      }
9763
9764      function collectNextLine() {
9765        var buffer = self.buffer;
9766        var pos = 0;
9767
9768        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
9769          ++pos;
9770        }
9771
9772        var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
9773
9774        if (buffer[pos] === '\r') {
9775          ++pos;
9776        }
9777
9778        if (buffer[pos] === '\n') {
9779          ++pos;
9780        }
9781
9782        self.buffer = buffer.substr(pos);
9783        return line;
9784      } // 3.4 WebVTT region and WebVTT region settings syntax
9785
9786
9787      function parseRegion(input) {
9788        var settings = new Settings();
9789        parseOptions(input, function (k, v) {
9790          switch (k) {
9791            case "id":
9792              settings.set(k, v);
9793              break;
9794
9795            case "width":
9796              settings.percent(k, v);
9797              break;
9798
9799            case "lines":
9800              settings.integer(k, v);
9801              break;
9802
9803            case "regionanchor":
9804            case "viewportanchor":
9805              var xy = v.split(',');
9806
9807              if (xy.length !== 2) {
9808                break;
9809              } // We have to make sure both x and y parse, so use a temporary
9810              // settings object here.
9811
9812
9813              var anchor = new Settings();
9814              anchor.percent("x", xy[0]);
9815              anchor.percent("y", xy[1]);
9816
9817              if (!anchor.has("x") || !anchor.has("y")) {
9818                break;
9819              }
9820
9821              settings.set(k + "X", anchor.get("x"));
9822              settings.set(k + "Y", anchor.get("y"));
9823              break;
9824
9825            case "scroll":
9826              settings.alt(k, v, ["up"]);
9827              break;
9828          }
9829        }, /=/, /\s/); // Create the region, using default values for any values that were not
9830        // specified.
9831
9832        if (settings.has("id")) {
9833          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9834          region.width = settings.get("width", 100);
9835          region.lines = settings.get("lines", 3);
9836          region.regionAnchorX = settings.get("regionanchorX", 0);
9837          region.regionAnchorY = settings.get("regionanchorY", 100);
9838          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9839          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9840          region.scroll = settings.get("scroll", ""); // Register the region.
9841
9842          self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
9843          // reference it.
9844
9845          self.regionList.push({
9846            id: settings.get("id"),
9847            region: region
9848          });
9849        }
9850      } // draft-pantos-http-live-streaming-20
9851      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9852      // 3.5 WebVTT
9853
9854
9855      function parseTimestampMap(input) {
9856        var settings = new Settings();
9857        parseOptions(input, function (k, v) {
9858          switch (k) {
9859            case "MPEGT":
9860              settings.integer(k + 'S', v);
9861              break;
9862
9863            case "LOCA":
9864              settings.set(k + 'L', parseTimeStamp(v));
9865              break;
9866          }
9867        }, /[^\d]:/, /,/);
9868        self.ontimestampmap && self.ontimestampmap({
9869          "MPEGTS": settings.get("MPEGTS"),
9870          "LOCAL": settings.get("LOCAL")
9871        });
9872      } // 3.2 WebVTT metadata header syntax
9873
9874
9875      function parseHeader(input) {
9876        if (input.match(/X-TIMESTAMP-MAP/)) {
9877          // This line contains HLS X-TIMESTAMP-MAP metadata
9878          parseOptions(input, function (k, v) {
9879            switch (k) {
9880              case "X-TIMESTAMP-MAP":
9881                parseTimestampMap(v);
9882                break;
9883            }
9884          }, /=/);
9885        } else {
9886          parseOptions(input, function (k, v) {
9887            switch (k) {
9888              case "Region":
9889                // 3.3 WebVTT region metadata header syntax
9890                parseRegion(v);
9891                break;
9892            }
9893          }, /:/);
9894        }
9895      } // 5.1 WebVTT file parsing.
9896
9897
vendor: 4,445 bytes, lines 9898-10045
9898      try {
9899        var line;
9900
9901        if (self.state === "INITIAL") {
9902          // We can't start parsing until we have the first line.
9903          if (!/\r\n|\n/.test(self.buffer)) {
9904            return this;
9905          }
9906
9907          line = collectNextLine();
9908          var m = line.match(/^WEBVTT([ \t].*)?$/);
9909
9910          if (!m || !m[0]) {
9911            throw new ParsingError(ParsingError.Errors.BadSignature);
9912          }
9913
9914          self.state = "HEADER";
9915        }
9916
9917        var alreadyCollectedLine = false;
9918
9919        while (self.buffer) {
9920          // We can't parse a line until we have the full line.
9921          if (!/\r\n|\n/.test(self.buffer)) {
9922            return this;
9923          }
9924
9925          if (!alreadyCollectedLine) {
9926            line = collectNextLine();
9927          } else {
9928            alreadyCollectedLine = false;
9929          }
9930
9931          switch (self.state) {
9932            case "HEADER":
9933              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9934              if (/:/.test(line)) {
9935                parseHeader(line);
9936              } else if (!line) {
9937                // An empty line terminates the header and starts the body (cues).
9938                self.state = "ID";
9939              }
9940
9941              continue;
9942
9943            case "NOTE":
9944              // Ignore NOTE blocks.
9945              if (!line) {
9946                self.state = "ID";
9947              }
9948
9949              continue;
9950
9951            case "ID":
9952              // Check for the start of NOTE blocks.
9953              if (/^NOTE($|[ \t])/.test(line)) {
9954                self.state = "NOTE";
9955                break;
9956              } // 19-29 - Allow any number of line terminators, then initialize new cue values.
9957
9958
9959              if (!line) {
9960                continue;
9961              }
9962
9963              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center
9964
9965              try {
9966                self.cue.align = "center";
9967              } catch (e) {
9968                self.cue.align = "middle";
9969              }
9970
9971              self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
9972
9973              if (line.indexOf("-->") === -1) {
9974                self.cue.id = line;
9975                continue;
9976              }
9977
9978            // Process line as start of a cue.
9979
9980            /*falls through*/
9981
9982            case "CUE":
9983              // 40 - Collect cue timings and settings.
9984              try {
9985                parseCue(line, self.cue, self.regionList);
9986              } catch (e) {
9987                self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
9988
9989                self.cue = null;
9990                self.state = "BADCUE";
9991                continue;
9992              }
9993
9994              self.state = "CUETEXT";
9995              continue;
9996
9997            case "CUETEXT":
9998              var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
9999              // 35 - If we have the special substring '-->' then report the cue,
10000              // but do not collect the line as we need to process the current
10001              // one as a new cue.
10002
10003              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
10004                // We are done parsing self cue.
10005                self.oncue && self.oncue(self.cue);
10006                self.cue = null;
10007                self.state = "ID";
10008                continue;
10009              }
10010
10011              if (self.cue.text) {
10012                self.cue.text += "\n";
10013              }
10014
10015              self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
10016              continue;
10017
10018            case "BADCUE":
10019              // BADCUE
10020              // 54-62 - Collect and discard the remaining cue.
10021              if (!line) {
10022                self.state = "ID";
10023              }
10024
10025              continue;
10026          }
10027        }
10028      } catch (e) {
10029        self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
10030
10031        if (self.state === "CUETEXT" && self.cue && self.oncue) {
10032          self.oncue(self.cue);
10033        }
10034
10035        self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
10036        // another exception occurred so enter BADCUE state.
10037
10038        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
10039      }
10040
10041      return this;
10042    },
10043    flush: function flush() {
10044      var self = this;
10045
vendor: 8,966 bytes, lines 10046-10378
10046      try {
10047        // Finish decoding the stream.
10048        self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
10049
10050        if (self.cue || self.state === "HEADER") {
10051          self.buffer += "\n\n";
10052          self.parse();
10053        } // If we've flushed, parsed, and we're still on the INITIAL state then
10054        // that means we don't have enough of the stream to parse the first
10055        // line.
10056
10057
10058        if (self.state === "INITIAL") {
10059          throw new ParsingError(ParsingError.Errors.BadSignature);
10060        }
10061      } catch (e) {
10062        self.reportOrThrowError(e);
10063      }
10064
10065      self.onflush && self.onflush();
10066      return this;
10067    }
10068  };
10069  var vtt = WebVTT$1;
10070
10071  /**
10072   * Copyright 2013 vtt.js Contributors
10073   *
10074   * Licensed under the Apache License, Version 2.0 (the "License");
10075   * you may not use this file except in compliance with the License.
10076   * You may obtain a copy of the License at
10077   *
10078   *   http://www.apache.org/licenses/LICENSE-2.0
10079   *
10080   * Unless required by applicable law or agreed to in writing, software
10081   * distributed under the License is distributed on an "AS IS" BASIS,
10082   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10083   * See the License for the specific language governing permissions and
10084   * limitations under the License.
10085   */
10086  var autoKeyword = "auto";
10087  var directionSetting = {
10088    "": 1,
10089    "lr": 1,
10090    "rl": 1
10091  };
10092  var alignSetting = {
10093    "start": 1,
10094    "center": 1,
10095    "end": 1,
10096    "left": 1,
10097    "right": 1,
10098    "auto": 1,
10099    "line-left": 1,
10100    "line-right": 1
10101  };
10102
10103  function findDirectionSetting(value) {
10104    if (typeof value !== "string") {
10105      return false;
10106    }
10107
10108    var dir = directionSetting[value.toLowerCase()];
10109    return dir ? value.toLowerCase() : false;
10110  }
10111
10112  function findAlignSetting(value) {
10113    if (typeof value !== "string") {
10114      return false;
10115    }
10116
10117    var align = alignSetting[value.toLowerCase()];
10118    return align ? value.toLowerCase() : false;
10119  }
10120
10121  function VTTCue(startTime, endTime, text) {
10122    /**
10123     * Shim implementation specific properties. These properties are not in
10124     * the spec.
10125     */
10126    // Lets us know when the VTTCue's data has changed in such a way that we need
10127    // to recompute its display state. This lets us compute its display state
10128    // lazily.
10129    this.hasBeenReset = false;
10130    /**
10131     * VTTCue and TextTrackCue properties
10132     * http://dev.w3.org/html5/webvtt/#vttcue-interface
10133     */
10134
10135    var _id = "";
10136    var _pauseOnExit = false;
10137    var _startTime = startTime;
10138    var _endTime = endTime;
10139    var _text = text;
10140    var _region = null;
10141    var _vertical = "";
10142    var _snapToLines = true;
10143    var _line = "auto";
10144    var _lineAlign = "start";
10145    var _position = "auto";
10146    var _positionAlign = "auto";
10147    var _size = 100;
10148    var _align = "center";
10149    Object.defineProperties(this, {
10150      "id": {
10151        enumerable: true,
10152        get: function get() {
10153          return _id;
10154        },
10155        set: function set(value) {
10156          _id = "" + value;
10157        }
10158      },
10159      "pauseOnExit": {
10160        enumerable: true,
10161        get: function get() {
10162          return _pauseOnExit;
10163        },
10164        set: function set(value) {
10165          _pauseOnExit = !!value;
10166        }
10167      },
10168      "startTime": {
10169        enumerable: true,
10170        get: function get() {
10171          return _startTime;
10172        },
10173        set: function set(value) {
10174          if (typeof value !== "number") {
10175            throw new TypeError("Start time must be set to a number.");
10176          }
10177
10178          _startTime = value;
10179          this.hasBeenReset = true;
10180        }
10181      },
10182      "endTime": {
10183        enumerable: true,
10184        get: function get() {
10185          return _endTime;
10186        },
10187        set: function set(value) {
10188          if (typeof value !== "number") {
10189            throw new TypeError("End time must be set to a number.");
10190          }
10191
10192          _endTime = value;
10193          this.hasBeenReset = true;
10194        }
10195      },
10196      "text": {
10197        enumerable: true,
10198        get: function get() {
10199          return _text;
10200        },
10201        set: function set(value) {
10202          _text = "" + value;
10203          this.hasBeenReset = true;
10204        }
10205      },
10206      "region": {
10207        enumerable: true,
10208        get: function get() {
10209          return _region;
10210        },
10211        set: function set(value) {
10212          _region = value;
10213          this.hasBeenReset = true;
10214        }
10215      },
10216      "vertical": {
10217        enumerable: true,
10218        get: function get() {
10219          return _vertical;
10220        },
10221        set: function set(value) {
10222          var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
10223
10224          if (setting === false) {
10225            throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
10226          }
10227
10228          _vertical = setting;
10229          this.hasBeenReset = true;
10230        }
10231      },
10232      "snapToLines": {
10233        enumerable: true,
10234        get: function get() {
10235          return _snapToLines;
10236        },
10237        set: function set(value) {
10238          _snapToLines = !!value;
10239          this.hasBeenReset = true;
10240        }
10241      },
10242      "line": {
10243        enumerable: true,
10244        get: function get() {
10245          return _line;
10246        },
10247        set: function set(value) {
10248          if (typeof value !== "number" && value !== autoKeyword) {
10249            throw new SyntaxError("Line: an invalid number or illegal string was specified.");
10250          }
10251
10252          _line = value;
10253          this.hasBeenReset = true;
10254        }
10255      },
10256      "lineAlign": {
10257        enumerable: true,
10258        get: function get() {
10259          return _lineAlign;
10260        },
10261        set: function set(value) {
10262          var setting = findAlignSetting(value);
10263
10264          if (!setting) {
10265            console.warn("lineAlign: an invalid or illegal string was specified.");
10266          } else {
10267            _lineAlign = setting;
10268            this.hasBeenReset = true;
10269          }
10270        }
10271      },
10272      "position": {
10273        enumerable: true,
10274        get: function get() {
10275          return _position;
10276        },
10277        set: function set(value) {
10278          if (value < 0 || value > 100) {
10279            throw new Error("Position must be between 0 and 100.");
10280          }
10281
10282          _position = value;
10283          this.hasBeenReset = true;
10284        }
10285      },
10286      "positionAlign": {
10287        enumerable: true,
10288        get: function get() {
10289          return _positionAlign;
10290        },
10291        set: function set(value) {
10292          var setting = findAlignSetting(value);
10293
10294          if (!setting) {
10295            console.warn("positionAlign: an invalid or illegal string was specified.");
10296          } else {
10297            _positionAlign = setting;
10298            this.hasBeenReset = true;
10299          }
10300        }
10301      },
10302      "size": {
10303        enumerable: true,
10304        get: function get() {
10305          return _size;
10306        },
10307        set: function set(value) {
10308          if (value < 0 || value > 100) {
10309            throw new Error("Size must be between 0 and 100.");
10310          }
10311
10312          _size = value;
10313          this.hasBeenReset = true;
10314        }
10315      },
10316      "align": {
10317        enumerable: true,
10318        get: function get() {
10319          return _align;
10320        },
10321        set: function set(value) {
10322          var setting = findAlignSetting(value);
10323
10324          if (!setting) {
10325            throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
10326          }
10327
10328          _align = setting;
10329          this.hasBeenReset = true;
10330        }
10331      }
10332    });
10333    /**
10334     * Other <track> spec defined properties
10335     */
10336    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
10337
10338    this.displayState = undefined;
10339  }
10340  /**
10341   * VTTCue methods
10342   */
10343
10344
10345  VTTCue.prototype.getCueAsHTML = function () {
10346    // Assume WebVTT.convertCueToDOMTree is on the global.
10347    return WebVTT.convertCueToDOMTree(window, this.text);
10348  };
10349
10350  var vttcue = VTTCue;
10351
10352  /**
10353   * Copyright 2013 vtt.js Contributors
10354   *
10355   * Licensed under the Apache License, Version 2.0 (the "License");
10356   * you may not use this file except in compliance with the License.
10357   * You may obtain a copy of the License at
10358   *
10359   *   http://www.apache.org/licenses/LICENSE-2.0
10360   *
10361   * Unless required by applicable law or agreed to in writing, software
10362   * distributed under the License is distributed on an "AS IS" BASIS,
10363   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10364   * See the License for the specific language governing permissions and
10365   * limitations under the License.
10366   */
10367  var scrollSetting = {
10368    "": true,
10369    "up": true
10370  };
10371
10372  function findScrollSetting(value) {
10373    if (typeof value !== "string") {
10374      return false;
10375    }
10376
10377    var scroll = scrollSetting[value.toLowerCase()];
10378    return scroll ? value.toLowerCase() : false;
10379  }
10380
10381  function isValidPercentValue(value) {
10382    return typeof value === "number" && value >= 0 && value <= 100;
10383  } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
10384
10385
10386  function VTTRegion() {
10387    var _width = 100;
10388    var _lines = 3;
10389    var _regionAnchorX = 0;
10390    var _regionAnchorY = 100;
10391    var _viewportAnchorX = 0;
10392    var _viewportAnchorY = 100;
10393    var _scroll = "";
10394    Object.defineProperties(this, {
10395      "width": {
10396        enumerable: true,
10397        get: function get() {
10398          return _width;
10399        },
10400        set: function set(value) {
10401          if (!isValidPercentValue(value)) {
10402            throw new Error("Width must be between 0 and 100.");
10403          }
10404
10405          _width = value;
10406        }
10407      },
10408      "lines": {
10409        enumerable: true,
10410        get: function get() {
10411          return _lines;
10412        },
10413        set: function set(value) {
10414          if (typeof value !== "number") {
10415            throw new TypeError("Lines must be set to a number.");
10416          }
10417
10418          _lines = value;
10419        }
10420      },
10421      "regionAnchorY": {
10422        enumerable: true,
10423        get: function get() {
10424          return _regionAnchorY;
10425        },
10426        set: function set(value) {
10427          if (!isValidPercentValue(value)) {
10428            throw new Error("RegionAnchorX must be between 0 and 100.");
10429          }
10430
10431          _regionAnchorY = value;
10432        }
10433      },
10434      "regionAnchorX": {
10435        enumerable: true,
10436        get: function get() {
10437          return _regionAnchorX;
10438        },
10439        set: function set(value) {
10440          if (!isValidPercentValue(value)) {
10441            throw new Error("RegionAnchorY must be between 0 and 100.");
10442          }
10443
10444          _regionAnchorX = value;
10445        }
10446      },
10447      "viewportAnchorY": {
10448        enumerable: true,
10449        get: function get() {
10450          return _viewportAnchorY;
10451        },
10452        set: function set(value) {
10453          if (!isValidPercentValue(value)) {
10454            throw new Error("ViewportAnchorY must be between 0 and 100.");
10455          }
10456
10457          _viewportAnchorY = value;
10458        }
10459      },
10460      "viewportAnchorX": {
10461        enumerable: true,
10462        get: function get() {
10463          return _viewportAnchorX;
10464        },
10465        set: function set(value) {
10466          if (!isValidPercentValue(value)) {
10467            throw new Error("ViewportAnchorX must be between 0 and 100.");
10468          }
10469
10470          _viewportAnchorX = value;
10471        }
10472      },
10473      "scroll": {
10474        enumerable: true,
10475        get: function get() {
10476          return _scroll;
10477        },
10478        set: function set(value) {
10479          var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
10480
10481          if (setting === false) {
10482            console.warn("Scroll: an invalid or illegal string was specified.");
10483          } else {
10484            _scroll = setting;
10485          }
10486        }
10487      }
10488    });
10489  }
10490
10491  var vttregion = VTTRegion;
10492
10493  var browserIndex = createCommonjsModule(function (module) {
10494    /**
10495     * Copyright 2013 vtt.js Contributors
10496     *
10497     * Licensed under the Apache License, Version 2.0 (the "License");
10498     * you may not use this file except in compliance with the License.
10499     * You may obtain a copy of the License at
10500     *
10501     *   http://www.apache.org/licenses/LICENSE-2.0
10502     *
10503     * Unless required by applicable law or agreed to in writing, software
10504     * distributed under the License is distributed on an "AS IS" BASIS,
10505     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10506     * See the License for the specific language governing permissions and
10507     * limitations under the License.
10508     */
10509    // Default exports for Node. Export the extended versions of VTTCue and
10510    // VTTRegion in Node since we likely want the capability to convert back and
10511    // forth between JSON. If we don't then it's not that big of a deal since we're
10512    // off browser.
10513    var vttjs = module.exports = {
10514      WebVTT: vtt,
10515      VTTCue: vttcue,
10516      VTTRegion: vttregion
10517    };
10518    window$1.vttjs = vttjs;
10519    window$1.WebVTT = vttjs.WebVTT;
10520    var cueShim = vttjs.VTTCue;
10521    var regionShim = vttjs.VTTRegion;
10522    var nativeVTTCue = window$1.VTTCue;
10523    var nativeVTTRegion = window$1.VTTRegion;
10524
10525    vttjs.shim = function () {
10526      window$1.VTTCue = cueShim;
10527      window$1.VTTRegion = regionShim;
10528    };
10529
10530    vttjs.restore = function () {
10531      window$1.VTTCue = nativeVTTCue;
10532      window$1.VTTRegion = nativeVTTRegion;
10533    };
10534
10535    if (!window$1.VTTCue) {
10536      vttjs.shim();
10537    }
10538  });
10539  var browserIndex_1 = browserIndex.WebVTT;
10540  var browserIndex_2 = browserIndex.VTTCue;
10541  var browserIndex_3 = browserIndex.VTTRegion;
10542
10543  /**
10544   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10545   * that just contains the src url alone.
10546   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10547     * `var SourceString = 'http://example.com/some-video.mp4';`
10548   *
10549   * @typedef {Object|string} Tech~SourceObject
10550   *
10551   * @property {string} src
10552   *           The url to the source
10553   *
10554   * @property {string} type
10555   *           The mime type of the source
10556   */
10557
10558  /**
10559   * A function used by {@link Tech} to create a new {@link TextTrack}.
10560   *
10561   * @private
10562   *
10563   * @param {Tech} self
10564   *        An instance of the Tech class.
10565   *
10566   * @param {string} kind
10567   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10568   *
10569   * @param {string} [label]
10570   *        Label to identify the text track
10571   *
10572   * @param {string} [language]
10573   *        Two letter language abbreviation
10574   *
10575   * @param {Object} [options={}]
10576   *        An object with additional text track options
10577   *
10578   * @return {TextTrack}
10579   *          The text track that was created.
10580   */
10581
10582  function createTrackHelper(self, kind, label, language, options) {
10583    if (options === void 0) {
10584      options = {};
10585    }
10586
10587    var tracks = self.textTracks();
10588    options.kind = kind;
10589
10590    if (label) {
10591      options.label = label;
10592    }
10593
10594    if (language) {
10595      options.language = language;
10596    }
10597
10598    options.tech = self;
10599    var track = new ALL.text.TrackClass(options);
10600    tracks.addTrack(track);
10601    return track;
10602  }
10603  /**
10604   * This is the base class for media playback technology controllers, such as
10605   * {@link Flash} and {@link HTML5}
10606   *
10607   * @extends Component
10608   */
10609
10610
10611  var Tech = /*#__PURE__*/function (_Component) {
10612    inheritsLoose(Tech, _Component);
10613
10614    /**
10615    * Create an instance of this Tech.
10616    *
10617    * @param {Object} [options]
10618    *        The key/value store of player options.
10619    *
10620    * @param {Component~ReadyCallback} ready
10621    *        Callback function to call when the `HTML5` Tech is ready.
10622    */
10623    function Tech(options, ready) {
10624      var _this;
10625
10626      if (options === void 0) {
10627        options = {};
10628      }
10629
10630      if (ready === void 0) {
10631        ready = function ready() {};
10632      }
10633
10634      // we don't want the tech to report user activity automatically.
vendor: 38,861 bytes, lines 10635-11982
10635      // This is done manually in addControlsListeners
10636      options.reportTouchActivity = false;
10637      _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
10638      // implement a very limited played()
10639
10640      _this.hasStarted_ = false;
10641
10642      _this.on('playing', function () {
10643        this.hasStarted_ = true;
10644      });
10645
10646      _this.on('loadstart', function () {
10647        this.hasStarted_ = false;
10648      });
10649
10650      ALL.names.forEach(function (name) {
10651        var props = ALL[name];
10652
10653        if (options && options[props.getterName]) {
10654          _this[props.privateName] = options[props.getterName];
10655        }
10656      }); // Manually track progress in cases where the browser/flash player doesn't report it.
10657
10658      if (!_this.featuresProgressEvents) {
10659        _this.manualProgressOn();
10660      } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
10661
10662
10663      if (!_this.featuresTimeupdateEvents) {
10664        _this.manualTimeUpdatesOn();
10665      }
10666
10667      ['Text', 'Audio', 'Video'].forEach(function (track) {
10668        if (options["native" + track + "Tracks"] === false) {
10669          _this["featuresNative" + track + "Tracks"] = false;
10670        }
10671      });
10672
10673      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
10674        _this.featuresNativeTextTracks = false;
10675      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
10676        _this.featuresNativeTextTracks = true;
10677      }
10678
10679      if (!_this.featuresNativeTextTracks) {
10680        _this.emulateTextTracks();
10681      }
10682
10683      _this.preloadTextTracks = options.preloadTextTracks !== false;
10684      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
10685
10686      _this.initTrackListeners(); // Turn on component tap events only if not using native controls
10687
10688
10689      if (!options.nativeControlsForTouch) {
10690        _this.emitTapEvents();
10691      }
10692
10693      if (_this.constructor) {
10694        _this.name_ = _this.constructor.name || 'Unknown Tech';
10695      }
10696
10697      return _this;
10698    }
10699    /**
10700     * A special function to trigger source set in a way that will allow player
10701     * to re-trigger if the player or tech are not ready yet.
10702     *
10703     * @fires Tech#sourceset
10704     * @param {string} src The source string at the time of the source changing.
10705     */
10706
10707
10708    var _proto = Tech.prototype;
10709
10710    _proto.triggerSourceset = function triggerSourceset(src) {
10711      var _this2 = this;
10712
10713      if (!this.isReady_) {
10714        // on initial ready we have to trigger source set
10715        // 1ms after ready so that player can watch for it.
10716        this.one('ready', function () {
10717          return _this2.setTimeout(function () {
10718            return _this2.triggerSourceset(src);
10719          }, 1);
10720        });
10721      }
10722      /**
10723       * Fired when the source is set on the tech causing the media element
10724       * to reload.
10725       *
10726       * @see {@link Player#event:sourceset}
10727       * @event Tech#sourceset
10728       * @type {EventTarget~Event}
10729       */
10730
10731
10732      this.trigger({
10733        src: src,
10734        type: 'sourceset'
10735      });
10736    }
10737    /* Fallbacks for unsupported event types
10738    ================================================================================ */
10739
10740    /**
10741     * Polyfill the `progress` event for browsers that don't support it natively.
10742     *
10743     * @see {@link Tech#trackProgress}
10744     */
10745    ;
10746
10747    _proto.manualProgressOn = function manualProgressOn() {
10748      this.on('durationchange', this.onDurationChange);
10749      this.manualProgress = true; // Trigger progress watching when a source begins loading
10750
10751      this.one('ready', this.trackProgress);
10752    }
10753    /**
10754     * Turn off the polyfill for `progress` events that was created in
10755     * {@link Tech#manualProgressOn}
10756     */
10757    ;
10758
10759    _proto.manualProgressOff = function manualProgressOff() {
10760      this.manualProgress = false;
10761      this.stopTrackingProgress();
10762      this.off('durationchange', this.onDurationChange);
10763    }
10764    /**
10765     * This is used to trigger a `progress` event when the buffered percent changes. It
10766     * sets an interval function that will be called every 500 milliseconds to check if the
10767     * buffer end percent has changed.
10768     *
10769     * > This function is called by {@link Tech#manualProgressOn}
10770     *
10771     * @param {EventTarget~Event} event
10772     *        The `ready` event that caused this to run.
10773     *
10774     * @listens Tech#ready
10775     * @fires Tech#progress
10776     */
10777    ;
10778
10779    _proto.trackProgress = function trackProgress(event) {
10780      this.stopTrackingProgress();
10781      this.progressInterval = this.setInterval(bind(this, function () {
10782        // Don't trigger unless buffered amount is greater than last time
10783        var numBufferedPercent = this.bufferedPercent();
10784
10785        if (this.bufferedPercent_ !== numBufferedPercent) {
10786          /**
10787           * See {@link Player#progress}
10788           *
10789           * @event Tech#progress
10790           * @type {EventTarget~Event}
10791           */
10792          this.trigger('progress');
10793        }
10794
10795        this.bufferedPercent_ = numBufferedPercent;
10796
10797        if (numBufferedPercent === 1) {
10798          this.stopTrackingProgress();
10799        }
10800      }), 500);
10801    }
10802    /**
10803     * Update our internal duration on a `durationchange` event by calling
10804     * {@link Tech#duration}.
10805     *
10806     * @param {EventTarget~Event} event
10807     *        The `durationchange` event that caused this to run.
10808     *
10809     * @listens Tech#durationchange
10810     */
10811    ;
10812
10813    _proto.onDurationChange = function onDurationChange(event) {
10814      this.duration_ = this.duration();
10815    }
10816    /**
10817     * Get and create a `TimeRange` object for buffering.
10818     *
10819     * @return {TimeRange}
10820     *         The time range object that was created.
10821     */
10822    ;
10823
10824    _proto.buffered = function buffered() {
10825      return createTimeRanges(0, 0);
10826    }
10827    /**
10828     * Get the percentage of the current video that is currently buffered.
10829     *
10830     * @return {number}
10831     *         A number from 0 to 1 that represents the decimal percentage of the
10832     *         video that is buffered.
10833     *
10834     */
10835    ;
10836
10837    _proto.bufferedPercent = function bufferedPercent$1() {
10838      return bufferedPercent(this.buffered(), this.duration_);
10839    }
10840    /**
10841     * Turn off the polyfill for `progress` events that was created in
10842     * {@link Tech#manualProgressOn}
10843     * Stop manually tracking progress events by clearing the interval that was set in
10844     * {@link Tech#trackProgress}.
10845     */
10846    ;
10847
10848    _proto.stopTrackingProgress = function stopTrackingProgress() {
10849      this.clearInterval(this.progressInterval);
10850    }
10851    /**
10852     * Polyfill the `timeupdate` event for browsers that don't support it.
10853     *
10854     * @see {@link Tech#trackCurrentTime}
10855     */
10856    ;
10857
10858    _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10859      this.manualTimeUpdates = true;
10860      this.on('play', this.trackCurrentTime);
10861      this.on('pause', this.stopTrackingCurrentTime);
10862    }
10863    /**
10864     * Turn off the polyfill for `timeupdate` events that was created in
10865     * {@link Tech#manualTimeUpdatesOn}
10866     */
10867    ;
10868
10869    _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10870      this.manualTimeUpdates = false;
10871      this.stopTrackingCurrentTime();
10872      this.off('play', this.trackCurrentTime);
10873      this.off('pause', this.stopTrackingCurrentTime);
10874    }
10875    /**
10876     * Sets up an interval function to track current time and trigger `timeupdate` every
10877     * 250 milliseconds.
10878     *
10879     * @listens Tech#play
10880     * @triggers Tech#timeupdate
10881     */
10882    ;
10883
10884    _proto.trackCurrentTime = function trackCurrentTime() {
10885      if (this.currentTimeInterval) {
10886        this.stopTrackingCurrentTime();
10887      }
10888
10889      this.currentTimeInterval = this.setInterval(function () {
10890        /**
10891         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10892         *
10893         * @event Tech#timeupdate
10894         * @type {EventTarget~Event}
10895         */
10896        this.trigger({
10897          type: 'timeupdate',
10898          target: this,
10899          manuallyTriggered: true
10900        }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10901      }, 250);
10902    }
10903    /**
10904     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10905     * `timeupdate` event is no longer triggered.
10906     *
10907     * @listens {Tech#pause}
10908     */
10909    ;
10910
10911    _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10912      this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
10913      // the progress bar won't make it all the way to the end
10914
10915      this.trigger({
10916        type: 'timeupdate',
10917        target: this,
10918        manuallyTriggered: true
10919      });
10920    }
10921    /**
10922     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10923     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10924     *
10925     * @fires Component#dispose
10926     */
10927    ;
10928
10929    _proto.dispose = function dispose() {
10930      // clear out all tracks because we can't reuse them between techs
10931      this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
10932
10933      if (this.manualProgress) {
10934        this.manualProgressOff();
10935      }
10936
10937      if (this.manualTimeUpdates) {
10938        this.manualTimeUpdatesOff();
10939      }
10940
10941      _Component.prototype.dispose.call(this);
10942    }
10943    /**
10944     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10945     *
10946     * > Note: Techs without source handlers should call this between sources for `video`
10947     *         & `audio` tracks. You don't want to use them between tracks!
10948     *
10949     * @param {string[]|string} types
10950     *        TrackList names to clear, valid names are `video`, `audio`, and
10951     *        `text`.
10952     */
10953    ;
10954
10955    _proto.clearTracks = function clearTracks(types) {
10956      var _this3 = this;
10957
10958      types = [].concat(types); // clear out all tracks because we can't reuse them between techs
10959
10960      types.forEach(function (type) {
10961        var list = _this3[type + "Tracks"]() || [];
10962        var i = list.length;
10963
10964        while (i--) {
10965          var track = list[i];
10966
10967          if (type === 'text') {
10968            _this3.removeRemoteTextTrack(track);
10969          }
10970
10971          list.removeTrack(track);
10972        }
10973      });
10974    }
10975    /**
10976     * Remove any TextTracks added via addRemoteTextTrack that are
10977     * flagged for automatic garbage collection
10978     */
10979    ;
10980
10981    _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10982      var list = this.autoRemoteTextTracks_ || [];
10983      var i = list.length;
10984
10985      while (i--) {
10986        var track = list[i];
10987        this.removeRemoteTextTrack(track);
10988      }
10989    }
10990    /**
10991     * Reset the tech, which will removes all sources and reset the internal readyState.
10992     *
10993     * @abstract
10994     */
10995    ;
10996
10997    _proto.reset = function reset() {}
10998    /**
10999     * Get the value of `crossOrigin` from the tech.
11000     *
11001     * @abstract
11002     *
11003     * @see {Html5#crossOrigin}
11004     */
11005    ;
11006
11007    _proto.crossOrigin = function crossOrigin() {}
11008    /**
11009     * Set the value of `crossOrigin` on the tech.
11010     *
11011     * @abstract
11012     *
11013     * @param {string} crossOrigin the crossOrigin value
11014     * @see {Html5#setCrossOrigin}
11015     */
11016    ;
11017
11018    _proto.setCrossOrigin = function setCrossOrigin() {}
11019    /**
11020     * Get or set an error on the Tech.
11021     *
11022     * @param {MediaError} [err]
11023     *        Error to set on the Tech
11024     *
11025     * @return {MediaError|null}
11026     *         The current error object on the tech, or null if there isn't one.
11027     */
11028    ;
11029
11030    _proto.error = function error(err) {
11031      if (err !== undefined) {
11032        this.error_ = new MediaError(err);
11033        this.trigger('error');
11034      }
11035
11036      return this.error_;
11037    }
11038    /**
11039     * Returns the `TimeRange`s that have been played through for the current source.
11040     *
11041     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
11042     *         It only checks whether the source has played at all or not.
11043     *
11044     * @return {TimeRange}
11045     *         - A single time range if this video has played
11046     *         - An empty set of ranges if not.
11047     */
11048    ;
11049
11050    _proto.played = function played() {
11051      if (this.hasStarted_) {
11052        return createTimeRanges(0, 0);
11053      }
11054
11055      return createTimeRanges();
11056    }
11057    /**
11058     * Set whether we are scrubbing or not
11059     *
11060     * @abstract
11061     *
11062     * @see {Html5#setScrubbing}
11063     */
11064    ;
11065
11066    _proto.setScrubbing = function setScrubbing() {}
11067    /**
11068     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
11069     * previously called.
11070     *
11071     * @fires Tech#timeupdate
11072     */
11073    ;
11074
11075    _proto.setCurrentTime = function setCurrentTime() {
11076      // improve the accuracy of manual timeupdates
11077      if (this.manualTimeUpdates) {
11078        /**
11079         * A manual `timeupdate` event.
11080         *
11081         * @event Tech#timeupdate
11082         * @type {EventTarget~Event}
11083         */
11084        this.trigger({
11085          type: 'timeupdate',
11086          target: this,
11087          manuallyTriggered: true
11088        });
11089      }
11090    }
11091    /**
11092     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
11093     * {@link TextTrackList} events.
11094     *
11095     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
11096     *
11097     * @fires Tech#audiotrackchange
11098     * @fires Tech#videotrackchange
11099     * @fires Tech#texttrackchange
11100     */
11101    ;
11102
11103    _proto.initTrackListeners = function initTrackListeners() {
11104      var _this4 = this;
11105
11106      /**
11107        * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
11108        *
11109        * @event Tech#audiotrackchange
11110        * @type {EventTarget~Event}
11111        */
11112
11113      /**
11114        * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
11115        *
11116        * @event Tech#videotrackchange
11117        * @type {EventTarget~Event}
11118        */
11119
11120      /**
11121        * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
11122        *
11123        * @event Tech#texttrackchange
11124        * @type {EventTarget~Event}
11125        */
11126      NORMAL.names.forEach(function (name) {
11127        var props = NORMAL[name];
11128
11129        var trackListChanges = function trackListChanges() {
11130          _this4.trigger(name + "trackchange");
11131        };
11132
11133        var tracks = _this4[props.getterName]();
11134
11135        tracks.addEventListener('removetrack', trackListChanges);
11136        tracks.addEventListener('addtrack', trackListChanges);
11137
11138        _this4.on('dispose', function () {
11139          tracks.removeEventListener('removetrack', trackListChanges);
11140          tracks.removeEventListener('addtrack', trackListChanges);
11141        });
11142      });
11143    }
11144    /**
11145     * Emulate TextTracks using vtt.js if necessary
11146     *
11147     * @fires Tech#vttjsloaded
11148     * @fires Tech#vttjserror
11149     */
11150    ;
11151
11152    _proto.addWebVttScript_ = function addWebVttScript_() {
11153      var _this5 = this;
11154
11155      if (window$1.WebVTT) {
11156        return;
11157      } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
11158      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
11159      // before inserting the WebVTT script
11160
11161
11162      if (document.body.contains(this.el())) {
11163        // load via require if available and vtt.js script location was not passed in
11164        // as an option. novtt builds will turn the above require call into an empty object
11165        // which will cause this if check to always fail.
11166        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
11167          this.trigger('vttjsloaded');
11168          return;
11169        } // load vtt.js via the script location option or the cdn of no location was
11170        // passed in
11171
11172
11173        var script = document.createElement('script');
11174        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
11175
11176        script.onload = function () {
11177          /**
11178           * Fired when vtt.js is loaded.
11179           *
11180           * @event Tech#vttjsloaded
11181           * @type {EventTarget~Event}
11182           */
11183          _this5.trigger('vttjsloaded');
11184        };
11185
11186        script.onerror = function () {
11187          /**
11188           * Fired when vtt.js was not loaded due to an error
11189           *
11190           * @event Tech#vttjsloaded
11191           * @type {EventTarget~Event}
11192           */
11193          _this5.trigger('vttjserror');
11194        };
11195
11196        this.on('dispose', function () {
11197          script.onload = null;
11198          script.onerror = null;
11199        }); // but have not loaded yet and we set it to true before the inject so that
11200        // we don't overwrite the injected window.WebVTT if it loads right away
11201
11202        window$1.WebVTT = true;
11203        this.el().parentNode.appendChild(script);
11204      } else {
11205        this.ready(this.addWebVttScript_);
11206      }
11207    }
11208    /**
11209     * Emulate texttracks
11210     *
11211     */
11212    ;
11213
11214    _proto.emulateTextTracks = function emulateTextTracks() {
11215      var _this6 = this;
11216
11217      var tracks = this.textTracks();
11218      var remoteTracks = this.remoteTextTracks();
11219
11220      var handleAddTrack = function handleAddTrack(e) {
11221        return tracks.addTrack(e.track);
11222      };
11223
11224      var handleRemoveTrack = function handleRemoveTrack(e) {
11225        return tracks.removeTrack(e.track);
11226      };
11227
11228      remoteTracks.on('addtrack', handleAddTrack);
11229      remoteTracks.on('removetrack', handleRemoveTrack);
11230      this.addWebVttScript_();
11231
11232      var updateDisplay = function updateDisplay() {
11233        return _this6.trigger('texttrackchange');
11234      };
11235
11236      var textTracksChanges = function textTracksChanges() {
11237        updateDisplay();
11238
11239        for (var i = 0; i < tracks.length; i++) {
11240          var track = tracks[i];
11241          track.removeEventListener('cuechange', updateDisplay);
11242
11243          if (track.mode === 'showing') {
11244            track.addEventListener('cuechange', updateDisplay);
11245          }
11246        }
11247      };
11248
11249      textTracksChanges();
11250      tracks.addEventListener('change', textTracksChanges);
11251      tracks.addEventListener('addtrack', textTracksChanges);
11252      tracks.addEventListener('removetrack', textTracksChanges);
11253      this.on('dispose', function () {
11254        remoteTracks.off('addtrack', handleAddTrack);
11255        remoteTracks.off('removetrack', handleRemoveTrack);
11256        tracks.removeEventListener('change', textTracksChanges);
11257        tracks.removeEventListener('addtrack', textTracksChanges);
11258        tracks.removeEventListener('removetrack', textTracksChanges);
11259
11260        for (var i = 0; i < tracks.length; i++) {
11261          var track = tracks[i];
11262          track.removeEventListener('cuechange', updateDisplay);
11263        }
11264      });
11265    }
11266    /**
11267     * Create and returns a remote {@link TextTrack} object.
11268     *
11269     * @param {string} kind
11270     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
11271     *
11272     * @param {string} [label]
11273     *        Label to identify the text track
11274     *
11275     * @param {string} [language]
11276     *        Two letter language abbreviation
11277     *
11278     * @return {TextTrack}
11279     *         The TextTrack that gets created.
11280     */
11281    ;
11282
11283    _proto.addTextTrack = function addTextTrack(kind, label, language) {
11284      if (!kind) {
11285        throw new Error('TextTrack kind is required but was not provided');
11286      }
11287
11288      return createTrackHelper(this, kind, label, language);
11289    }
11290    /**
11291     * Create an emulated TextTrack for use by addRemoteTextTrack
11292     *
11293     * This is intended to be overridden by classes that inherit from
11294     * Tech in order to create native or custom TextTracks.
11295     *
11296     * @param {Object} options
11297     *        The object should contain the options to initialize the TextTrack with.
11298     *
11299     * @param {string} [options.kind]
11300     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
11301     *
11302     * @param {string} [options.label].
11303     *        Label to identify the text track
11304     *
11305     * @param {string} [options.language]
11306     *        Two letter language abbreviation.
11307     *
11308     * @return {HTMLTrackElement}
11309     *         The track element that gets created.
11310     */
11311    ;
11312
11313    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
11314      var track = mergeOptions(options, {
11315        tech: this
11316      });
11317      return new REMOTE.remoteTextEl.TrackClass(track);
11318    }
11319    /**
11320     * Creates a remote text track object and returns an html track element.
11321     *
11322     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
11323     *
11324     * @param {Object} options
11325     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
11326     *
11327     * @param {boolean} [manualCleanup=true]
11328     *        - When false: the TextTrack will be automatically removed from the video
11329     *          element whenever the source changes
11330     *        - When True: The TextTrack will have to be cleaned up manually
11331     *
11332     * @return {HTMLTrackElement}
11333     *         An Html Track Element.
11334     *
11335     * @deprecated The default functionality for this function will be equivalent
11336     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
11337     *             also be removed.
11338     */
11339    ;
11340
11341    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
11342      var _this7 = this;
11343
11344      if (options === void 0) {
11345        options = {};
11346      }
11347
11348      var htmlTrackElement = this.createRemoteTextTrack(options);
11349
11350      if (manualCleanup !== true && manualCleanup !== false) {
11351        // deprecation warning
11352        log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
11353        manualCleanup = true;
11354      } // store HTMLTrackElement and TextTrack to remote list
11355
11356
11357      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
11358      this.remoteTextTracks().addTrack(htmlTrackElement.track);
11359
11360      if (manualCleanup !== true) {
11361        // create the TextTrackList if it doesn't exist
11362        this.ready(function () {
11363          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
11364        });
11365      }
11366
11367      return htmlTrackElement;
11368    }
11369    /**
11370     * Remove a remote text track from the remote `TextTrackList`.
11371     *
11372     * @param {TextTrack} track
11373     *        `TextTrack` to remove from the `TextTrackList`
11374     */
11375    ;
11376
11377    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
11378      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
11379
11380      this.remoteTextTrackEls().removeTrackElement_(trackElement);
11381      this.remoteTextTracks().removeTrack(track);
11382      this.autoRemoteTextTracks_.removeTrack(track);
11383    }
11384    /**
11385     * Gets available media playback quality metrics as specified by the W3C's Media
11386     * Playback Quality API.
11387     *
11388     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
11389     *
11390     * @return {Object}
11391     *         An object with supported media playback quality metrics
11392     *
11393     * @abstract
11394     */
11395    ;
11396
11397    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
11398      return {};
11399    }
11400    /**
11401     * Attempt to create a floating video window always on top of other windows
11402     * so that users may continue consuming media while they interact with other
11403     * content sites, or applications on their device.
11404     *
11405     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
11406     *
11407     * @return {Promise|undefined}
11408     *         A promise with a Picture-in-Picture window if the browser supports
11409     *         Promises (or one was passed in as an option). It returns undefined
11410     *         otherwise.
11411     *
11412     * @abstract
11413     */
11414    ;
11415
11416    _proto.requestPictureInPicture = function requestPictureInPicture() {
11417      var PromiseClass = this.options_.Promise || window$1.Promise;
11418
11419      if (PromiseClass) {
11420        return PromiseClass.reject();
11421      }
11422    }
11423    /**
11424     * A method to check for the value of the 'disablePictureInPicture' <video> property.
11425     * Defaults to true, as it should be considered disabled if the tech does not support pip
11426     *
11427     * @abstract
11428     */
11429    ;
11430
11431    _proto.disablePictureInPicture = function disablePictureInPicture() {
11432      return true;
11433    }
11434    /**
11435     * A method to set or unset the 'disablePictureInPicture' <video> property.
11436     *
11437     * @abstract
11438     */
11439    ;
11440
11441    _proto.setDisablePictureInPicture = function setDisablePictureInPicture() {}
11442    /**
11443     * A method to set a poster from a `Tech`.
11444     *
11445     * @abstract
11446     */
11447    ;
11448
11449    _proto.setPoster = function setPoster() {}
11450    /**
11451     * A method to check for the presence of the 'playsinline' <video> attribute.
11452     *
11453     * @abstract
11454     */
11455    ;
11456
11457    _proto.playsinline = function playsinline() {}
11458    /**
11459     * A method to set or unset the 'playsinline' <video> attribute.
11460     *
11461     * @abstract
11462     */
11463    ;
11464
11465    _proto.setPlaysinline = function setPlaysinline() {}
11466    /**
11467     * Attempt to force override of native audio tracks.
11468     *
11469     * @param {boolean} override - If set to true native audio will be overridden,
11470     * otherwise native audio will potentially be used.
11471     *
11472     * @abstract
11473     */
11474    ;
11475
11476    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
11477    /**
11478     * Attempt to force override of native video tracks.
11479     *
11480     * @param {boolean} override - If set to true native video will be overridden,
11481     * otherwise native video will potentially be used.
11482     *
11483     * @abstract
11484     */
11485    ;
11486
11487    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
11488    /*
11489     * Check if the tech can support the given mime-type.
11490     *
11491     * The base tech does not support any type, but source handlers might
11492     * overwrite this.
11493     *
11494     * @param  {string} type
11495     *         The mimetype to check for support
11496     *
11497     * @return {string}
11498     *         'probably', 'maybe', or empty string
11499     *
11500     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11501     *
11502     * @abstract
11503     */
11504    ;
11505
11506    _proto.canPlayType = function canPlayType() {
11507      return '';
11508    }
11509    /**
11510     * Check if the type is supported by this tech.
11511     *
11512     * The base tech does not support any type, but source handlers might
11513     * overwrite this.
11514     *
11515     * @param {string} type
11516     *        The media type to check
11517     * @return {string} Returns the native video element's response
11518     */
11519    ;
11520
11521    Tech.canPlayType = function canPlayType() {
11522      return '';
11523    }
11524    /**
11525     * Check if the tech can support the given source
11526     *
11527     * @param {Object} srcObj
11528     *        The source object
11529     * @param {Object} options
11530     *        The options passed to the tech
11531     * @return {string} 'probably', 'maybe', or '' (empty string)
11532     */
11533    ;
11534
11535    Tech.canPlaySource = function canPlaySource(srcObj, options) {
11536      return Tech.canPlayType(srcObj.type);
11537    }
11538    /*
11539     * Return whether the argument is a Tech or not.
11540     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11541     *
11542     * @param {Object} component
11543     *        The item to check
11544     *
11545     * @return {boolean}
11546     *         Whether it is a tech or not
11547     *         - True if it is a tech
11548     *         - False if it is not
11549     */
11550    ;
11551
11552    Tech.isTech = function isTech(component) {
11553      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11554    }
11555    /**
11556     * Registers a `Tech` into a shared list for videojs.
11557     *
11558     * @param {string} name
11559     *        Name of the `Tech` to register.
11560     *
11561     * @param {Object} tech
11562     *        The `Tech` class to register.
11563     */
11564    ;
11565
11566    Tech.registerTech = function registerTech(name, tech) {
11567      if (!Tech.techs_) {
11568        Tech.techs_ = {};
11569      }
11570
11571      if (!Tech.isTech(tech)) {
11572        throw new Error("Tech " + name + " must be a Tech");
11573      }
11574
11575      if (!Tech.canPlayType) {
11576        throw new Error('Techs must have a static canPlayType method on them');
11577      }
11578
11579      if (!Tech.canPlaySource) {
11580        throw new Error('Techs must have a static canPlaySource method on them');
11581      }
11582
11583      name = toTitleCase(name);
11584      Tech.techs_[name] = tech;
11585      Tech.techs_[toLowerCase(name)] = tech;
11586
11587      if (name !== 'Tech') {
11588        // camel case the techName for use in techOrder
11589        Tech.defaultTechOrder_.push(name);
11590      }
11591
11592      return tech;
11593    }
11594    /**
11595     * Get a `Tech` from the shared list by name.
11596     *
11597     * @param {string} name
11598     *        `camelCase` or `TitleCase` name of the Tech to get
11599     *
11600     * @return {Tech|undefined}
11601     *         The `Tech` or undefined if there was no tech with the name requested.
11602     */
11603    ;
11604
11605    Tech.getTech = function getTech(name) {
11606      if (!name) {
11607        return;
11608      }
11609
11610      if (Tech.techs_ && Tech.techs_[name]) {
11611        return Tech.techs_[name];
11612      }
11613
11614      name = toTitleCase(name);
11615
11616      if (window$1 && window$1.videojs && window$1.videojs[name]) {
11617        log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
11618        return window$1.videojs[name];
11619      }
11620    };
11621
11622    return Tech;
11623  }(Component);
11624  /**
11625   * Get the {@link VideoTrackList}
11626   *
11627   * @returns {VideoTrackList}
11628   * @method Tech.prototype.videoTracks
11629   */
11630
11631  /**
11632   * Get the {@link AudioTrackList}
11633   *
11634   * @returns {AudioTrackList}
11635   * @method Tech.prototype.audioTracks
11636   */
11637
11638  /**
11639   * Get the {@link TextTrackList}
11640   *
11641   * @returns {TextTrackList}
11642   * @method Tech.prototype.textTracks
11643   */
11644
11645  /**
11646   * Get the remote element {@link TextTrackList}
11647   *
11648   * @returns {TextTrackList}
11649   * @method Tech.prototype.remoteTextTracks
11650   */
11651
11652  /**
11653   * Get the remote element {@link HtmlTrackElementList}
11654   *
11655   * @returns {HtmlTrackElementList}
11656   * @method Tech.prototype.remoteTextTrackEls
11657   */
11658
11659
11660  ALL.names.forEach(function (name) {
11661    var props = ALL[name];
11662
11663    Tech.prototype[props.getterName] = function () {
11664      this[props.privateName] = this[props.privateName] || new props.ListClass();
11665      return this[props.privateName];
11666    };
11667  });
11668  /**
11669   * List of associated text tracks
11670   *
11671   * @type {TextTrackList}
11672   * @private
11673   * @property Tech#textTracks_
11674   */
11675
11676  /**
11677   * List of associated audio tracks.
11678   *
11679   * @type {AudioTrackList}
11680   * @private
11681   * @property Tech#audioTracks_
11682   */
11683
11684  /**
11685   * List of associated video tracks.
11686   *
11687   * @type {VideoTrackList}
11688   * @private
11689   * @property Tech#videoTracks_
11690   */
11691
11692  /**
11693   * Boolean indicating whether the `Tech` supports volume control.
11694   *
11695   * @type {boolean}
11696   * @default
11697   */
11698
11699  Tech.prototype.featuresVolumeControl = true;
11700  /**
11701   * Boolean indicating whether the `Tech` supports muting volume.
11702   *
11703   * @type {bolean}
11704   * @default
11705   */
11706
11707  Tech.prototype.featuresMuteControl = true;
11708  /**
11709   * Boolean indicating whether the `Tech` supports fullscreen resize control.
11710   * Resizing plugins using request fullscreen reloads the plugin
11711   *
11712   * @type {boolean}
11713   * @default
11714   */
11715
11716  Tech.prototype.featuresFullscreenResize = false;
11717  /**
11718   * Boolean indicating whether the `Tech` supports changing the speed at which the video
11719   * plays. Examples:
11720   *   - Set player to play 2x (twice) as fast
11721   *   - Set player to play 0.5x (half) as fast
11722   *
11723   * @type {boolean}
11724   * @default
11725   */
11726
11727  Tech.prototype.featuresPlaybackRate = false;
11728  /**
11729   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
11730   * not triggered by video-js-swf. This will be used to determine if
11731   * {@link Tech#manualProgressOn} should be called.
11732   *
11733   * @type {boolean}
11734   * @default
11735   */
11736
11737  Tech.prototype.featuresProgressEvents = false;
11738  /**
11739   * Boolean indicating whether the `Tech` supports the `sourceset` event.
11740   *
11741   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
11742   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
11743   * a new source.
11744   *
11745   * @type {boolean}
11746   * @default
11747   */
11748
11749  Tech.prototype.featuresSourceset = false;
11750  /**
11751   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
11752   * not triggered by video-js-swf. This will be used to determine if
11753   * {@link Tech#manualTimeUpdates} should be called.
11754   *
11755   * @type {boolean}
11756   * @default
11757   */
11758
11759  Tech.prototype.featuresTimeupdateEvents = false;
11760  /**
11761   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
11762   * This will help us integrate with native `TextTrack`s if the browser supports them.
11763   *
11764   * @type {boolean}
11765   * @default
11766   */
11767
11768  Tech.prototype.featuresNativeTextTracks = false;
11769  /**
11770   * A functional mixin for techs that want to use the Source Handler pattern.
11771   * Source handlers are scripts for handling specific formats.
11772   * The source handler pattern is used for adaptive formats (HLS, DASH) that
11773   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
11774   * Example: `Tech.withSourceHandlers.call(MyTech);`
11775   *
11776   * @param {Tech} _Tech
11777   *        The tech to add source handler functions to.
11778   *
11779   * @mixes Tech~SourceHandlerAdditions
11780   */
11781
11782  Tech.withSourceHandlers = function (_Tech) {
11783    /**
11784     * Register a source handler
11785     *
11786     * @param {Function} handler
11787     *        The source handler class
11788     *
11789     * @param {number} [index]
11790     *        Register it at the following index
11791     */
11792    _Tech.registerSourceHandler = function (handler, index) {
11793      var handlers = _Tech.sourceHandlers;
11794
11795      if (!handlers) {
11796        handlers = _Tech.sourceHandlers = [];
11797      }
11798
11799      if (index === undefined) {
11800        // add to the end of the list
11801        index = handlers.length;
11802      }
11803
11804      handlers.splice(index, 0, handler);
11805    };
11806    /**
11807     * Check if the tech can support the given type. Also checks the
11808     * Techs sourceHandlers.
11809     *
11810     * @param {string} type
11811     *         The mimetype to check.
11812     *
11813     * @return {string}
11814     *         'probably', 'maybe', or '' (empty string)
11815     */
11816
11817
11818    _Tech.canPlayType = function (type) {
11819      var handlers = _Tech.sourceHandlers || [];
11820      var can;
11821
11822      for (var i = 0; i < handlers.length; i++) {
11823        can = handlers[i].canPlayType(type);
11824
11825        if (can) {
11826          return can;
11827        }
11828      }
11829
11830      return '';
11831    };
11832    /**
11833     * Returns the first source handler that supports the source.
11834     *
11835     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
11836     *
11837     * @param {Tech~SourceObject} source
11838     *        The source object
11839     *
11840     * @param {Object} options
11841     *        The options passed to the tech
11842     *
11843     * @return {SourceHandler|null}
11844     *          The first source handler that supports the source or null if
11845     *          no SourceHandler supports the source
11846     */
11847
11848
11849    _Tech.selectSourceHandler = function (source, options) {
11850      var handlers = _Tech.sourceHandlers || [];
11851      var can;
11852
11853      for (var i = 0; i < handlers.length; i++) {
11854        can = handlers[i].canHandleSource(source, options);
11855
11856        if (can) {
11857          return handlers[i];
11858        }
11859      }
11860
11861      return null;
11862    };
11863    /**
11864     * Check if the tech can support the given source.
11865     *
11866     * @param {Tech~SourceObject} srcObj
11867     *        The source object
11868     *
11869     * @param {Object} options
11870     *        The options passed to the tech
11871     *
11872     * @return {string}
11873     *         'probably', 'maybe', or '' (empty string)
11874     */
11875
11876
11877    _Tech.canPlaySource = function (srcObj, options) {
11878      var sh = _Tech.selectSourceHandler(srcObj, options);
11879
11880      if (sh) {
11881        return sh.canHandleSource(srcObj, options);
11882      }
11883
11884      return '';
11885    };
11886    /**
11887     * When using a source handler, prefer its implementation of
11888     * any function normally provided by the tech.
11889     */
11890
11891
11892    var deferrable = ['seekable', 'seeking', 'duration'];
11893    /**
11894     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11895     * function if it exists, with a fallback to the Techs seekable function.
11896     *
11897     * @method _Tech.seekable
11898     */
11899
11900    /**
11901     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11902     * function if it exists, otherwise it will fallback to the techs duration function.
11903     *
11904     * @method _Tech.duration
11905     */
11906
11907    deferrable.forEach(function (fnName) {
11908      var originalFn = this[fnName];
11909
11910      if (typeof originalFn !== 'function') {
11911        return;
11912      }
11913
11914      this[fnName] = function () {
11915        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11916          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11917        }
11918
11919        return originalFn.apply(this, arguments);
11920      };
11921    }, _Tech.prototype);
11922    /**
11923     * Create a function for setting the source using a source object
11924     * and source handlers.
11925     * Should never be called unless a source handler was found.
11926     *
11927     * @param {Tech~SourceObject} source
11928     *        A source object with src and type keys
11929     */
11930
11931    _Tech.prototype.setSource = function (source) {
11932      var sh = _Tech.selectSourceHandler(source, this.options_);
11933
11934      if (!sh) {
11935        // Fall back to a native source hander when unsupported sources are
11936        // deliberately set
11937        if (_Tech.nativeSourceHandler) {
11938          sh = _Tech.nativeSourceHandler;
11939        } else {
11940          log.error('No source handler found for the current source.');
11941        }
11942      } // Dispose any existing source handler
11943
11944
11945      this.disposeSourceHandler();
11946      this.off('dispose', this.disposeSourceHandler);
11947
11948      if (sh !== _Tech.nativeSourceHandler) {
11949        this.currentSource_ = source;
11950      }
11951
11952      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11953      this.one('dispose', this.disposeSourceHandler);
11954    };
11955    /**
11956     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11957     *
11958     * @listens Tech#dispose
11959     */
11960
11961
11962    _Tech.prototype.disposeSourceHandler = function () {
11963      // if we have a source and get another one
11964      // then we are loading something new
11965      // than clear all of our current tracks
11966      if (this.currentSource_) {
11967        this.clearTracks(['audio', 'video']);
11968        this.currentSource_ = null;
11969      } // always clean up auto-text tracks
11970
11971
11972      this.cleanupAutoTextTracks();
11973
11974      if (this.sourceHandler_) {
11975        if (this.sourceHandler_.dispose) {
11976          this.sourceHandler_.dispose();
11977        }
11978
11979        this.sourceHandler_ = null;
11980      }
11981    };
11982  };
vendor: 4,340 bytes, lines 11982-12132
11982 // The base Tech class needs to be registered as a Component. It is the only
11983  // Tech that can be registered as a Component.
11984
11985
11986  Component.registerComponent('Tech', Tech);
11987  Tech.registerTech('Tech', Tech);
11988  /**
11989   * A list of techs that should be added to techOrder on Players
11990   *
11991   * @private
11992   */
11993
11994  Tech.defaultTechOrder_ = [];
11995
11996  /**
11997   * @file middleware.js
11998   * @module middleware
11999   */
12000  var middlewares = {};
12001  var middlewareInstances = {};
12002  var TERMINATOR = {};
12003  /**
12004   * A middleware object is a plain JavaScript object that has methods that
12005   * match the {@link Tech} methods found in the lists of allowed
12006   * {@link module:middleware.allowedGetters|getters},
12007   * {@link module:middleware.allowedSetters|setters}, and
12008   * {@link module:middleware.allowedMediators|mediators}.
12009   *
12010   * @typedef {Object} MiddlewareObject
12011   */
12012
12013  /**
12014   * A middleware factory function that should return a
12015   * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
12016   *
12017   * This factory will be called for each player when needed, with the player
12018   * passed in as an argument.
12019   *
12020   * @callback MiddlewareFactory
12021   * @param {Player} player
12022   *        A Video.js player.
12023   */
12024
12025  /**
12026   * Define a middleware that the player should use by way of a factory function
12027   * that returns a middleware object.
12028   *
12029   * @param  {string} type
12030   *         The MIME type to match or `"*"` for all MIME types.
12031   *
12032   * @param  {MiddlewareFactory} middleware
12033   *         A middleware factory function that will be executed for
12034   *         matching types.
12035   */
12036
12037  function use(type, middleware) {
12038    middlewares[type] = middlewares[type] || [];
12039    middlewares[type].push(middleware);
12040  }
12041  /**
12042   * Asynchronously sets a source using middleware by recursing through any
12043   * matching middlewares and calling `setSource` on each, passing along the
12044   * previous returned value each time.
12045   *
12046   * @param  {Player} player
12047   *         A {@link Player} instance.
12048   *
12049   * @param  {Tech~SourceObject} src
12050   *         A source object.
12051   *
12052   * @param  {Function}
12053   *         The next middleware to run.
12054   */
12055
12056  function setSource(player, src, next) {
12057    player.setTimeout(function () {
12058      return setSourceHelper(src, middlewares[src.type], next, player);
12059    }, 1);
12060  }
12061  /**
12062   * When the tech is set, passes the tech to each middleware's `setTech` method.
12063   *
12064   * @param {Object[]} middleware
12065   *        An array of middleware instances.
12066   *
12067   * @param {Tech} tech
12068   *        A Video.js tech.
12069   */
12070
12071  function setTech(middleware, tech) {
12072    middleware.forEach(function (mw) {
12073      return mw.setTech && mw.setTech(tech);
12074    });
12075  }
12076  /**
12077   * Calls a getter on the tech first, through each middleware
12078   * from right to left to the player.
12079   *
12080   * @param  {Object[]} middleware
12081   *         An array of middleware instances.
12082   *
12083   * @param  {Tech} tech
12084   *         The current tech.
12085   *
12086   * @param  {string} method
12087   *         A method name.
12088   *
12089   * @return {Mixed}
12090   *         The final value from the tech after middleware has intercepted it.
12091   */
12092
12093  function get(middleware, tech, method) {
12094    return middleware.reduceRight(middlewareIterator(method), tech[method]());
12095  }
12096  /**
12097   * Takes the argument given to the player and calls the setter method on each
12098   * middleware from left to right to the tech.
12099   *
12100   * @param  {Object[]} middleware
12101   *         An array of middleware instances.
12102   *
12103   * @param  {Tech} tech
12104   *         The current tech.
12105   *
12106   * @param  {string} method
12107   *         A method name.
12108   *
12109   * @param  {Mixed} arg
12110   *         The value to set on the tech.
12111   *
12112   * @return {Mixed}
12113   *         The return value of the `method` of the `tech`.
12114   */
12115
12116  function set(middleware, tech, method, arg) {
12117    return tech[method](middleware.reduce(middlewareIterator(method), arg));
12118  }
12119  /**
12120   * Takes the argument given to the player and calls the `call` version of the
12121   * method on each middleware from left to right.
12122   *
12123   * Then, call the passed in method on the tech and return the result unchanged
12124   * back to the player, through middleware, this time from right to left.
12125   *
12126   * @param  {Object[]} middleware
12127   *         An array of middleware instances.
12128   *
12129   * @param  {Tech} tech
12130   *         The current tech.
12131   *
12132   * @param  {string} method
vendor: 182,697 bytes, lines 12133-18508
12133   *         A method name.
12134   *
12135   * @param  {Mixed} arg
12136   *         The value to set on the tech.
12137   *
12138   * @return {Mixed}
12139   *         The return value of the `method` of the `tech`, regardless of the
12140   *         return values of middlewares.
12141   */
12142
12143  function mediate(middleware, tech, method, arg) {
12144    if (arg === void 0) {
12145      arg = null;
12146    }
12147
12148    var callMethod = 'call' + toTitleCase(method);
12149    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
12150    var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
12151    // prevent confusion if a techs method actually returns null.
12152
12153    var returnValue = terminated ? null : tech[method](middlewareValue);
12154    executeRight(middleware, method, returnValue, terminated);
12155    return returnValue;
12156  }
12157  /**
12158   * Enumeration of allowed getters where the keys are method names.
12159   *
12160   * @type {Object}
12161   */
12162
12163  var allowedGetters = {
12164    buffered: 1,
12165    currentTime: 1,
12166    duration: 1,
12167    muted: 1,
12168    played: 1,
12169    paused: 1,
12170    seekable: 1,
12171    volume: 1
12172  };
12173  /**
12174   * Enumeration of allowed setters where the keys are method names.
12175   *
12176   * @type {Object}
12177   */
12178
12179  var allowedSetters = {
12180    setCurrentTime: 1,
12181    setMuted: 1,
12182    setVolume: 1
12183  };
12184  /**
12185   * Enumeration of allowed mediators where the keys are method names.
12186   *
12187   * @type {Object}
12188   */
12189
12190  var allowedMediators = {
12191    play: 1,
12192    pause: 1
12193  };
12194
12195  function middlewareIterator(method) {
12196    return function (value, mw) {
12197      // if the previous middleware terminated, pass along the termination
12198      if (value === TERMINATOR) {
12199        return TERMINATOR;
12200      }
12201
12202      if (mw[method]) {
12203        return mw[method](value);
12204      }
12205
12206      return value;
12207    };
12208  }
12209
12210  function executeRight(mws, method, value, terminated) {
12211    for (var i = mws.length - 1; i >= 0; i--) {
12212      var mw = mws[i];
12213
12214      if (mw[method]) {
12215        mw[method](terminated, value);
12216      }
12217    }
12218  }
12219  /**
12220   * Clear the middleware cache for a player.
12221   *
12222   * @param  {Player} player
12223   *         A {@link Player} instance.
12224   */
12225
12226
12227  function clearCacheForPlayer(player) {
12228    middlewareInstances[player.id()] = null;
12229  }
12230  /**
12231   * {
12232   *  [playerId]: [[mwFactory, mwInstance], ...]
12233   * }
12234   *
12235   * @private
12236   */
12237
12238  function getOrCreateFactory(player, mwFactory) {
12239    var mws = middlewareInstances[player.id()];
12240    var mw = null;
12241
12242    if (mws === undefined || mws === null) {
12243      mw = mwFactory(player);
12244      middlewareInstances[player.id()] = [[mwFactory, mw]];
12245      return mw;
12246    }
12247
12248    for (var i = 0; i < mws.length; i++) {
12249      var _mws$i = mws[i],
12250          mwf = _mws$i[0],
12251          mwi = _mws$i[1];
12252
12253      if (mwf !== mwFactory) {
12254        continue;
12255      }
12256
12257      mw = mwi;
12258    }
12259
12260    if (mw === null) {
12261      mw = mwFactory(player);
12262      mws.push([mwFactory, mw]);
12263    }
12264
12265    return mw;
12266  }
12267
12268  function setSourceHelper(src, middleware, next, player, acc, lastRun) {
12269    if (src === void 0) {
12270      src = {};
12271    }
12272
12273    if (middleware === void 0) {
12274      middleware = [];
12275    }
12276
12277    if (acc === void 0) {
12278      acc = [];
12279    }
12280
12281    if (lastRun === void 0) {
12282      lastRun = false;
12283    }
12284
12285    var _middleware = middleware,
12286        mwFactory = _middleware[0],
12287        mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
12288
12289
12290    if (typeof mwFactory === 'string') {
12291      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
12292      // then call the mw's setSource method
12293    } else if (mwFactory) {
12294      var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
12295
12296      if (!mw.setSource) {
12297        acc.push(mw);
12298        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12299      }
12300
12301      mw.setSource(assign({}, src), function (err, _src) {
12302        // something happened, try the next middleware on the current level
12303        // make sure to use the old src
12304        if (err) {
12305          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12306        } // we've succeeded, now we need to go deeper
12307
12308
12309        acc.push(mw); // if it's the same type, continue down the current chain
12310        // otherwise, we want to go down the new chain
12311
12312        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
12313      });
12314    } else if (mwrest.length) {
12315      setSourceHelper(src, mwrest, next, player, acc, lastRun);
12316    } else if (lastRun) {
12317      next(src, acc);
12318    } else {
12319      setSourceHelper(src, middlewares['*'], next, player, acc, true);
12320    }
12321  }
12322
12323  /**
12324   * Mimetypes
12325   *
12326   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
12327   * @typedef Mimetypes~Kind
12328   * @enum
12329   */
12330
12331  var MimetypesKind = {
12332    opus: 'video/ogg',
12333    ogv: 'video/ogg',
12334    mp4: 'video/mp4',
12335    mov: 'video/mp4',
12336    m4v: 'video/mp4',
12337    mkv: 'video/x-matroska',
12338    m4a: 'audio/mp4',
12339    mp3: 'audio/mpeg',
12340    aac: 'audio/aac',
12341    caf: 'audio/x-caf',
12342    flac: 'audio/flac',
12343    oga: 'audio/ogg',
12344    wav: 'audio/wav',
12345    m3u8: 'application/x-mpegURL',
12346    jpg: 'image/jpeg',
12347    jpeg: 'image/jpeg',
12348    gif: 'image/gif',
12349    png: 'image/png',
12350    svg: 'image/svg+xml',
12351    webp: 'image/webp'
12352  };
12353  /**
12354   * Get the mimetype of a given src url if possible
12355   *
12356   * @param {string} src
12357   *        The url to the src
12358   *
12359   * @return {string}
12360   *         return the mimetype if it was known or empty string otherwise
12361   */
12362
12363  var getMimetype = function getMimetype(src) {
12364    if (src === void 0) {
12365      src = '';
12366    }
12367
12368    var ext = getFileExtension(src);
12369    var mimetype = MimetypesKind[ext.toLowerCase()];
12370    return mimetype || '';
12371  };
12372  /**
12373   * Find the mime type of a given source string if possible. Uses the player
12374   * source cache.
12375   *
12376   * @param {Player} player
12377   *        The player object
12378   *
12379   * @param {string} src
12380   *        The source string
12381   *
12382   * @return {string}
12383   *         The type that was found
12384   */
12385
12386  var findMimetype = function findMimetype(player, src) {
12387    if (!src) {
12388      return '';
12389    } // 1. check for the type in the `source` cache
12390
12391
12392    if (player.cache_.source.src === src && player.cache_.source.type) {
12393      return player.cache_.source.type;
12394    } // 2. see if we have this source in our `currentSources` cache
12395
12396
12397    var matchingSources = player.cache_.sources.filter(function (s) {
12398      return s.src === src;
12399    });
12400
12401    if (matchingSources.length) {
12402      return matchingSources[0].type;
12403    } // 3. look for the src url in source elements and use the type there
12404
12405
12406    var sources = player.$$('source');
12407
12408    for (var i = 0; i < sources.length; i++) {
12409      var s = sources[i];
12410
12411      if (s.type && s.src && s.src === src) {
12412        return s.type;
12413      }
12414    } // 4. finally fallback to our list of mime types based on src url extension
12415
12416
12417    return getMimetype(src);
12418  };
12419
12420  /**
12421   * @module filter-source
12422   */
12423  /**
12424   * Filter out single bad source objects or multiple source objects in an
12425   * array. Also flattens nested source object arrays into a 1 dimensional
12426   * array of source objects.
12427   *
12428   * @param {Tech~SourceObject|Tech~SourceObject[]} src
12429   *        The src object to filter
12430   *
12431   * @return {Tech~SourceObject[]}
12432   *         An array of sourceobjects containing only valid sources
12433   *
12434   * @private
12435   */
12436
12437  var filterSource = function filterSource(src) {
12438    // traverse array
12439    if (Array.isArray(src)) {
12440      var newsrc = [];
12441      src.forEach(function (srcobj) {
12442        srcobj = filterSource(srcobj);
12443
12444        if (Array.isArray(srcobj)) {
12445          newsrc = newsrc.concat(srcobj);
12446        } else if (isObject(srcobj)) {
12447          newsrc.push(srcobj);
12448        }
12449      });
12450      src = newsrc;
12451    } else if (typeof src === 'string' && src.trim()) {
12452      // convert string into object
12453      src = [fixSource({
12454        src: src
12455      })];
12456    } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
12457      // src is already valid
12458      src = [fixSource(src)];
12459    } else {
12460      // invalid source, turn it into an empty array
12461      src = [];
12462    }
12463
12464    return src;
12465  };
12466  /**
12467   * Checks src mimetype, adding it when possible
12468   *
12469   * @param {Tech~SourceObject} src
12470   *        The src object to check
12471   * @return {Tech~SourceObject}
12472   *        src Object with known type
12473   */
12474
12475
12476  function fixSource(src) {
12477    if (!src.type) {
12478      var mimetype = getMimetype(src.src);
12479
12480      if (mimetype) {
12481        src.type = mimetype;
12482      }
12483    }
12484
12485    return src;
12486  }
12487
12488  /**
12489   * The `MediaLoader` is the `Component` that decides which playback technology to load
12490   * when a player is initialized.
12491   *
12492   * @extends Component
12493   */
12494
12495  var MediaLoader = /*#__PURE__*/function (_Component) {
12496    inheritsLoose(MediaLoader, _Component);
12497
12498    /**
12499     * Create an instance of this class.
12500     *
12501     * @param {Player} player
12502     *        The `Player` that this class should attach to.
12503     *
12504     * @param {Object} [options]
12505     *        The key/value store of player options.
12506     *
12507     * @param {Component~ReadyCallback} [ready]
12508     *        The function that is run when this component is ready.
12509     */
12510    function MediaLoader(player, options, ready) {
12511      var _this;
12512
12513      // MediaLoader has no element
12514      var options_ = mergeOptions({
12515        createEl: false
12516      }, options);
12517      _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
12518      // load the first supported playback technology.
12519
12520      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
12521        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
12522          var techName = toTitleCase(j[i]);
12523          var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
12524          // Remove once that deprecated behavior is removed.
12525
12526          if (!techName) {
12527            tech = Component.getComponent(techName);
12528          } // Check if the browser supports this technology
12529
12530
12531          if (tech && tech.isSupported()) {
12532            player.loadTech_(techName);
12533            break;
12534          }
12535        }
12536      } else {
12537        // Loop through playback technologies (HTML5, Flash) and check for support.
12538        // Then load the best source.
12539        // A few assumptions here:
12540        //   All playback technologies respect preload false.
12541        player.src(options.playerOptions.sources);
12542      }
12543
12544      return _this;
12545    }
12546
12547    return MediaLoader;
12548  }(Component);
12549
12550  Component.registerComponent('MediaLoader', MediaLoader);
12551
12552  /**
12553   * Component which is clickable or keyboard actionable, but is not a
12554   * native HTML button.
12555   *
12556   * @extends Component
12557   */
12558
12559  var ClickableComponent = /*#__PURE__*/function (_Component) {
12560    inheritsLoose(ClickableComponent, _Component);
12561
12562    /**
12563     * Creates an instance of this class.
12564     *
12565     * @param  {Player} player
12566     *         The `Player` that this class should be attached to.
12567     *
12568     * @param  {Object} [options]
12569     *         The key/value store of player options.
12570     *
12571     * @param  {function} [options.clickHandler]
12572     *         The function to call when the button is clicked / activated
12573     */
12574    function ClickableComponent(player, options) {
12575      var _this;
12576
12577      _this = _Component.call(this, player, options) || this;
12578
12579      _this.emitTapEvents();
12580
12581      _this.enable();
12582
12583      return _this;
12584    }
12585    /**
12586     * Create the `ClickableComponent`s DOM element.
12587     *
12588     * @param {string} [tag=div]
12589     *        The element's node type.
12590     *
12591     * @param {Object} [props={}]
12592     *        An object of properties that should be set on the element.
12593     *
12594     * @param {Object} [attributes={}]
12595     *        An object of attributes that should be set on the element.
12596     *
12597     * @return {Element}
12598     *         The element that gets created.
12599     */
12600
12601
12602    var _proto = ClickableComponent.prototype;
12603
12604    _proto.createEl = function createEl(tag, props, attributes) {
12605      if (tag === void 0) {
12606        tag = 'div';
12607      }
12608
12609      if (props === void 0) {
12610        props = {};
12611      }
12612
12613      if (attributes === void 0) {
12614        attributes = {};
12615      }
12616
12617      props = assign({
12618        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12619        className: this.buildCSSClass(),
12620        tabIndex: 0
12621      }, props);
12622
12623      if (tag === 'button') {
12624        log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
12625      } // Add ARIA attributes for clickable element which is not a native HTML button
12626
12627
12628      attributes = assign({
12629        role: 'button'
12630      }, attributes);
12631      this.tabIndex_ = props.tabIndex;
12632
12633      var el = _Component.prototype.createEl.call(this, tag, props, attributes);
12634
12635      this.createControlTextEl(el);
12636      return el;
12637    };
12638
12639    _proto.dispose = function dispose() {
12640      // remove controlTextEl_ on dispose
12641      this.controlTextEl_ = null;
12642
12643      _Component.prototype.dispose.call(this);
12644    }
12645    /**
12646     * Create a control text element on this `ClickableComponent`
12647     *
12648     * @param {Element} [el]
12649     *        Parent element for the control text.
12650     *
12651     * @return {Element}
12652     *         The control text element that gets created.
12653     */
12654    ;
12655
12656    _proto.createControlTextEl = function createControlTextEl(el) {
12657      this.controlTextEl_ = createEl('span', {
12658        className: 'vjs-control-text'
12659      }, {
12660        // let the screen reader user know that the text of the element may change
12661        'aria-live': 'polite'
12662      });
12663
12664      if (el) {
12665        el.appendChild(this.controlTextEl_);
12666      }
12667
12668      this.controlText(this.controlText_, el);
12669      return this.controlTextEl_;
12670    }
12671    /**
12672     * Get or set the localize text to use for the controls on the `ClickableComponent`.
12673     *
12674     * @param {string} [text]
12675     *        Control text for element.
12676     *
12677     * @param {Element} [el=this.el()]
12678     *        Element to set the title on.
12679     *
12680     * @return {string}
12681     *         - The control text when getting
12682     */
12683    ;
12684
12685    _proto.controlText = function controlText(text, el) {
12686      if (el === void 0) {
12687        el = this.el();
12688      }
12689
12690      if (text === undefined) {
12691        return this.controlText_ || 'Need Text';
12692      }
12693
12694      var localizedText = this.localize(text);
12695      this.controlText_ = text;
12696      textContent(this.controlTextEl_, localizedText);
12697
12698      if (!this.nonIconControl) {
12699        // Set title attribute if only an icon is shown
12700        el.setAttribute('title', localizedText);
12701      }
12702    }
12703    /**
12704     * Builds the default DOM `className`.
12705     *
12706     * @return {string}
12707     *         The DOM `className` for this object.
12708     */
12709    ;
12710
12711    _proto.buildCSSClass = function buildCSSClass() {
12712      return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
12713    }
12714    /**
12715     * Enable this `ClickableComponent`
12716     */
12717    ;
12718
12719    _proto.enable = function enable() {
12720      if (!this.enabled_) {
12721        this.enabled_ = true;
12722        this.removeClass('vjs-disabled');
12723        this.el_.setAttribute('aria-disabled', 'false');
12724
12725        if (typeof this.tabIndex_ !== 'undefined') {
12726          this.el_.setAttribute('tabIndex', this.tabIndex_);
12727        }
12728
12729        this.on(['tap', 'click'], this.handleClick);
12730        this.on('keydown', this.handleKeyDown);
12731      }
12732    }
12733    /**
12734     * Disable this `ClickableComponent`
12735     */
12736    ;
12737
12738    _proto.disable = function disable() {
12739      this.enabled_ = false;
12740      this.addClass('vjs-disabled');
12741      this.el_.setAttribute('aria-disabled', 'true');
12742
12743      if (typeof this.tabIndex_ !== 'undefined') {
12744        this.el_.removeAttribute('tabIndex');
12745      }
12746
12747      this.off('mouseover', this.handleMouseOver);
12748      this.off('mouseout', this.handleMouseOut);
12749      this.off(['tap', 'click'], this.handleClick);
12750      this.off('keydown', this.handleKeyDown);
12751    }
12752    /**
12753     * Event handler that is called when a `ClickableComponent` receives a
12754     * `click` or `tap` event.
12755     *
12756     * @param {EventTarget~Event} event
12757     *        The `tap` or `click` event that caused this function to be called.
12758     *
12759     * @listens tap
12760     * @listens click
12761     * @abstract
12762     */
12763    ;
12764
12765    _proto.handleClick = function handleClick(event) {
12766      if (this.options_.clickHandler) {
12767        this.options_.clickHandler.call(this, arguments);
12768      }
12769    }
12770    /**
12771     * Event handler that is called when a `ClickableComponent` receives a
12772     * `keydown` event.
12773     *
12774     * By default, if the key is Space or Enter, it will trigger a `click` event.
12775     *
12776     * @param {EventTarget~Event} event
12777     *        The `keydown` event that caused this function to be called.
12778     *
12779     * @listens keydown
12780     */
12781    ;
12782
12783    _proto.handleKeyDown = function handleKeyDown(event) {
12784      // Support Space or Enter key operation to fire a click event. Also,
12785      // prevent the event from propagating through the DOM and triggering
12786      // Player hotkeys.
12787      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12788        event.preventDefault();
12789        event.stopPropagation();
12790        this.trigger('click');
12791      } else {
12792        // Pass keypress handling up for unsupported keys
12793        _Component.prototype.handleKeyDown.call(this, event);
12794      }
12795    };
12796
12797    return ClickableComponent;
12798  }(Component);
12799
12800  Component.registerComponent('ClickableComponent', ClickableComponent);
12801
12802  /**
12803   * A `ClickableComponent` that handles showing the poster image for the player.
12804   *
12805   * @extends ClickableComponent
12806   */
12807
12808  var PosterImage = /*#__PURE__*/function (_ClickableComponent) {
12809    inheritsLoose(PosterImage, _ClickableComponent);
12810
12811    /**
12812     * Create an instance of this class.
12813     *
12814     * @param {Player} player
12815     *        The `Player` that this class should attach to.
12816     *
12817     * @param {Object} [options]
12818     *        The key/value store of player options.
12819     */
12820    function PosterImage(player, options) {
12821      var _this;
12822
12823      _this = _ClickableComponent.call(this, player, options) || this;
12824
12825      _this.update();
12826
12827      player.on('posterchange', bind(assertThisInitialized(_this), _this.update));
12828      return _this;
12829    }
12830    /**
12831     * Clean up and dispose of the `PosterImage`.
12832     */
12833
12834
12835    var _proto = PosterImage.prototype;
12836
12837    _proto.dispose = function dispose() {
12838      this.player().off('posterchange', this.update);
12839
12840      _ClickableComponent.prototype.dispose.call(this);
12841    }
12842    /**
12843     * Create the `PosterImage`s DOM element.
12844     *
12845     * @return {Element}
12846     *         The element that gets created.
12847     */
12848    ;
12849
12850    _proto.createEl = function createEl$1() {
12851      var el = createEl('div', {
12852        className: 'vjs-poster',
12853        // Don't want poster to be tabbable.
12854        tabIndex: -1
12855      });
12856      return el;
12857    }
12858    /**
12859     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
12860     *
12861     * @listens Player#posterchange
12862     *
12863     * @param {EventTarget~Event} [event]
12864     *        The `Player#posterchange` event that triggered this function.
12865     */
12866    ;
12867
12868    _proto.update = function update(event) {
12869      var url = this.player().poster();
12870      this.setSrc(url); // If there's no poster source we should display:none on this component
12871      // so it's not still clickable or right-clickable
12872
12873      if (url) {
12874        this.show();
12875      } else {
12876        this.hide();
12877      }
12878    }
12879    /**
12880     * Set the source of the `PosterImage` depending on the display method.
12881     *
12882     * @param {string} url
12883     *        The URL to the source for the `PosterImage`.
12884     */
12885    ;
12886
12887    _proto.setSrc = function setSrc(url) {
12888      var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
12889      // this will throw an extra error
12890
12891      if (url) {
12892        backgroundImage = "url(\"" + url + "\")";
12893      }
12894
12895      this.el_.style.backgroundImage = backgroundImage;
12896    }
12897    /**
12898     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
12899     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
12900     *
12901     * @listens tap
12902     * @listens click
12903     * @listens keydown
12904     *
12905     * @param {EventTarget~Event} event
12906     +        The `click`, `tap` or `keydown` event that caused this function to be called.
12907     */
12908    ;
12909
12910    _proto.handleClick = function handleClick(event) {
12911      // We don't want a click to trigger playback when controls are disabled
12912      if (!this.player_.controls()) {
12913        return;
12914      }
12915
12916      var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
12917
12918      if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
12919      // calling .focus() on the video element causes the video to go black,
12920      // so we avoid it in that specific case
12921      !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
12922        this.player_.tech(true).focus();
12923      }
12924
12925      if (this.player_.paused()) {
12926        silencePromise(this.player_.play());
12927      } else {
12928        this.player_.pause();
12929      }
12930    };
12931
12932    return PosterImage;
12933  }(ClickableComponent);
12934
12935  Component.registerComponent('PosterImage', PosterImage);
12936
12937  var darkGray = '#222';
12938  var lightGray = '#ccc';
12939  var fontMap = {
12940    monospace: 'monospace',
12941    sansSerif: 'sans-serif',
12942    serif: 'serif',
12943    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
12944    monospaceSerif: '"Courier New", monospace',
12945    proportionalSansSerif: 'sans-serif',
12946    proportionalSerif: 'serif',
12947    casual: '"Comic Sans MS", Impact, fantasy',
12948    script: '"Monotype Corsiva", cursive',
12949    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
12950  };
12951  /**
12952   * Construct an rgba color from a given hex color code.
12953   *
12954   * @param {number} color
12955   *        Hex number for color, like #f0e or #f604e2.
12956   *
12957   * @param {number} opacity
12958   *        Value for opacity, 0.0 - 1.0.
12959   *
12960   * @return {string}
12961   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
12962   */
12963
12964  function constructColor(color, opacity) {
12965    var hex;
12966
12967    if (color.length === 4) {
12968      // color looks like "#f0e"
12969      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
12970    } else if (color.length === 7) {
12971      // color looks like "#f604e2"
12972      hex = color.slice(1);
12973    } else {
12974      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
12975    }
12976
12977    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
12978  }
12979  /**
12980   * Try to update the style of a DOM element. Some style changes will throw an error,
12981   * particularly in IE8. Those should be noops.
12982   *
12983   * @param {Element} el
12984   *        The DOM element to be styled.
12985   *
12986   * @param {string} style
12987   *        The CSS property on the element that should be styled.
12988   *
12989   * @param {string} rule
12990   *        The style rule that should be applied to the property.
12991   *
12992   * @private
12993   */
12994
12995  function tryUpdateStyle(el, style, rule) {
12996    try {
12997      el.style[style] = rule;
12998    } catch (e) {
12999      // Satisfies linter.
13000      return;
13001    }
13002  }
13003  /**
13004   * The component for displaying text track cues.
13005   *
13006   * @extends Component
13007   */
13008
13009
13010  var TextTrackDisplay = /*#__PURE__*/function (_Component) {
13011    inheritsLoose(TextTrackDisplay, _Component);
13012
13013    /**
13014     * Creates an instance of this class.
13015     *
13016     * @param {Player} player
13017     *        The `Player` that this class should be attached to.
13018     *
13019     * @param {Object} [options]
13020     *        The key/value store of player options.
13021     *
13022     * @param {Component~ReadyCallback} [ready]
13023     *        The function to call when `TextTrackDisplay` is ready.
13024     */
13025    function TextTrackDisplay(player, options, ready) {
13026      var _this;
13027
13028      _this = _Component.call(this, player, options, ready) || this;
13029      var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay);
13030      player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay));
13031      player.on('texttrackchange', updateDisplayHandler);
13032      player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error
13033      // if a track should show by default and the display hadn't loaded yet.
13034      // Should probably be moved to an external track loader when we support
13035      // tracks that don't need a display.
13036
13037      player.ready(bind(assertThisInitialized(_this), function () {
13038        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
13039          this.hide();
13040          return;
13041        }
13042
13043        player.on('fullscreenchange', updateDisplayHandler);
13044        player.on('playerresize', updateDisplayHandler);
13045        window$1.addEventListener('orientationchange', updateDisplayHandler);
13046        player.on('dispose', function () {
13047          return window$1.removeEventListener('orientationchange', updateDisplayHandler);
13048        });
13049        var tracks = this.options_.playerOptions.tracks || [];
13050
13051        for (var i = 0; i < tracks.length; i++) {
13052          this.player_.addRemoteTextTrack(tracks[i], true);
13053        }
13054
13055        this.preselectTrack();
13056      }));
13057      return _this;
13058    }
13059    /**
13060    * Preselect a track following this precedence:
13061    * - matches the previously selected {@link TextTrack}'s language and kind
13062    * - matches the previously selected {@link TextTrack}'s language only
13063    * - is the first default captions track
13064    * - is the first default descriptions track
13065    *
13066    * @listens Player#loadstart
13067    */
13068
13069
13070    var _proto = TextTrackDisplay.prototype;
13071
13072    _proto.preselectTrack = function preselectTrack() {
13073      var modes = {
13074        captions: 1,
13075        subtitles: 1
13076      };
13077      var trackList = this.player_.textTracks();
13078      var userPref = this.player_.cache_.selectedLanguage;
13079      var firstDesc;
13080      var firstCaptions;
13081      var preferredTrack;
13082
13083      for (var i = 0; i < trackList.length; i++) {
13084        var track = trackList[i];
13085
13086        if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
13087          // Always choose the track that matches both language and kind
13088          if (track.kind === userPref.kind) {
13089            preferredTrack = track; // or choose the first track that matches language
13090          } else if (!preferredTrack) {
13091            preferredTrack = track;
13092          } // clear everything if offTextTrackMenuItem was clicked
13093
13094        } else if (userPref && !userPref.enabled) {
13095          preferredTrack = null;
13096          firstDesc = null;
13097          firstCaptions = null;
13098        } else if (track["default"]) {
13099          if (track.kind === 'descriptions' && !firstDesc) {
13100            firstDesc = track;
13101          } else if (track.kind in modes && !firstCaptions) {
13102            firstCaptions = track;
13103          }
13104        }
13105      } // The preferredTrack matches the user preference and takes
13106      // precedence over all the other tracks.
13107      // So, display the preferredTrack before the first default track
13108      // and the subtitles/captions track before the descriptions track
13109
13110
13111      if (preferredTrack) {
13112        preferredTrack.mode = 'showing';
13113      } else if (firstCaptions) {
13114        firstCaptions.mode = 'showing';
13115      } else if (firstDesc) {
13116        firstDesc.mode = 'showing';
13117      }
13118    }
13119    /**
13120     * Turn display of {@link TextTrack}'s from the current state into the other state.
13121     * There are only two states:
13122     * - 'shown'
13123     * - 'hidden'
13124     *
13125     * @listens Player#loadstart
13126     */
13127    ;
13128
13129    _proto.toggleDisplay = function toggleDisplay() {
13130      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
13131        this.hide();
13132      } else {
13133        this.show();
13134      }
13135    }
13136    /**
13137     * Create the {@link Component}'s DOM element.
13138     *
13139     * @return {Element}
13140     *         The element that was created.
13141     */
13142    ;
13143
13144    _proto.createEl = function createEl() {
13145      return _Component.prototype.createEl.call(this, 'div', {
13146        className: 'vjs-text-track-display'
13147      }, {
13148        'aria-live': 'off',
13149        'aria-atomic': 'true'
13150      });
13151    }
13152    /**
13153     * Clear all displayed {@link TextTrack}s.
13154     */
13155    ;
13156
13157    _proto.clearDisplay = function clearDisplay() {
13158      if (typeof window$1.WebVTT === 'function') {
13159        window$1.WebVTT.processCues(window$1, [], this.el_);
13160      }
13161    }
13162    /**
13163     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
13164     * a {@link Player#fullscreenchange} is fired.
13165     *
13166     * @listens Player#texttrackchange
13167     * @listens Player#fullscreenchange
13168     */
13169    ;
13170
13171    _proto.updateDisplay = function updateDisplay() {
13172      var tracks = this.player_.textTracks();
13173      var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
13174      this.clearDisplay();
13175
13176      if (allowMultipleShowingTracks) {
13177        var showingTracks = [];
13178
13179        for (var _i = 0; _i < tracks.length; ++_i) {
13180          var track = tracks[_i];
13181
13182          if (track.mode !== 'showing') {
13183            continue;
13184          }
13185
13186          showingTracks.push(track);
13187        }
13188
13189        this.updateForTrack(showingTracks);
13190        return;
13191      } //  Track display prioritization model: if multiple tracks are 'showing',
13192      //  display the first 'subtitles' or 'captions' track which is 'showing',
13193      //  otherwise display the first 'descriptions' track which is 'showing'
13194
13195
13196      var descriptionsTrack = null;
13197      var captionsSubtitlesTrack = null;
13198      var i = tracks.length;
13199
13200      while (i--) {
13201        var _track = tracks[i];
13202
13203        if (_track.mode === 'showing') {
13204          if (_track.kind === 'descriptions') {
13205            descriptionsTrack = _track;
13206          } else {
13207            captionsSubtitlesTrack = _track;
13208          }
13209        }
13210      }
13211
13212      if (captionsSubtitlesTrack) {
13213        if (this.getAttribute('aria-live') !== 'off') {
13214          this.setAttribute('aria-live', 'off');
13215        }
13216
13217        this.updateForTrack(captionsSubtitlesTrack);
13218      } else if (descriptionsTrack) {
13219        if (this.getAttribute('aria-live') !== 'assertive') {
13220          this.setAttribute('aria-live', 'assertive');
13221        }
13222
13223        this.updateForTrack(descriptionsTrack);
13224      }
13225    }
13226    /**
13227     * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
13228     *
13229     * @param {TextTrack} track
13230     *        Text track object containing active cues to style.
13231     */
13232    ;
13233
13234    _proto.updateDisplayState = function updateDisplayState(track) {
13235      var overrides = this.player_.textTrackSettings.getValues();
13236      var cues = track.activeCues;
13237      var i = cues.length;
13238
13239      while (i--) {
13240        var cue = cues[i];
13241
13242        if (!cue) {
13243          continue;
13244        }
13245
13246        var cueDiv = cue.displayState;
13247
13248        if (overrides.color) {
13249          cueDiv.firstChild.style.color = overrides.color;
13250        }
13251
13252        if (overrides.textOpacity) {
13253          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
13254        }
13255
13256        if (overrides.backgroundColor) {
13257          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
13258        }
13259
13260        if (overrides.backgroundOpacity) {
13261          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
13262        }
13263
13264        if (overrides.windowColor) {
13265          if (overrides.windowOpacity) {
13266            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
13267          } else {
13268            cueDiv.style.backgroundColor = overrides.windowColor;
13269          }
13270        }
13271
13272        if (overrides.edgeStyle) {
13273          if (overrides.edgeStyle === 'dropshadow') {
13274            cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
13275          } else if (overrides.edgeStyle === 'raised') {
13276            cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
13277          } else if (overrides.edgeStyle === 'depressed') {
13278            cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
13279          } else if (overrides.edgeStyle === 'uniform') {
13280            cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
13281          }
13282        }
13283
13284        if (overrides.fontPercent && overrides.fontPercent !== 1) {
13285          var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
13286          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
13287          cueDiv.style.height = 'auto';
13288          cueDiv.style.top = 'auto';
13289        }
13290
13291        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
13292          if (overrides.fontFamily === 'small-caps') {
13293            cueDiv.firstChild.style.fontVariant = 'small-caps';
13294          } else {
13295            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
13296          }
13297        }
13298      }
13299    }
13300    /**
13301     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
13302     *
13303     * @param {TextTrack|TextTrack[]} tracks
13304     *        Text track object or text track array to be added to the list.
13305     */
13306    ;
13307
13308    _proto.updateForTrack = function updateForTrack(tracks) {
13309      if (!Array.isArray(tracks)) {
13310        tracks = [tracks];
13311      }
13312
13313      if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) {
13314        return !track.activeCues;
13315      })) {
13316        return;
13317      }
13318
13319      var cues = []; // push all active track cues
13320
13321      for (var i = 0; i < tracks.length; ++i) {
13322        var track = tracks[i];
13323
13324        for (var j = 0; j < track.activeCues.length; ++j) {
13325          cues.push(track.activeCues[j]);
13326        }
13327      } // removes all cues before it processes new ones
13328
13329
13330      window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
13331
13332      for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
13333        var _track2 = tracks[_i2];
13334
13335        for (var _j = 0; _j < _track2.activeCues.length; ++_j) {
13336          var cueEl = _track2.activeCues[_j].displayState;
13337          addClass(cueEl, 'vjs-text-track-cue');
13338          addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2));
13339        }
13340
13341        if (this.player_.textTrackSettings) {
13342          this.updateDisplayState(_track2);
13343        }
13344      }
13345    };
13346
13347    return TextTrackDisplay;
13348  }(Component);
13349
13350  Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
13351
13352  /**
13353   * A loading spinner for use during waiting/loading events.
13354   *
13355   * @extends Component
13356   */
13357
13358  var LoadingSpinner = /*#__PURE__*/function (_Component) {
13359    inheritsLoose(LoadingSpinner, _Component);
13360
13361    function LoadingSpinner() {
13362      return _Component.apply(this, arguments) || this;
13363    }
13364
13365    var _proto = LoadingSpinner.prototype;
13366
13367    /**
13368     * Create the `LoadingSpinner`s DOM element.
13369     *
13370     * @return {Element}
13371     *         The dom element that gets created.
13372     */
13373    _proto.createEl = function createEl$1() {
13374      var isAudio = this.player_.isAudio();
13375      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
13376      var controlText = createEl('span', {
13377        className: 'vjs-control-text',
13378        innerHTML: this.localize('{1} is loading.', [playerType])
13379      });
13380
13381      var el = _Component.prototype.createEl.call(this, 'div', {
13382        className: 'vjs-loading-spinner',
13383        dir: 'ltr'
13384      });
13385
13386      el.appendChild(controlText);
13387      return el;
13388    };
13389
13390    return LoadingSpinner;
13391  }(Component);
13392
13393  Component.registerComponent('LoadingSpinner', LoadingSpinner);
13394
13395  /**
13396   * Base class for all buttons.
13397   *
13398   * @extends ClickableComponent
13399   */
13400
13401  var Button = /*#__PURE__*/function (_ClickableComponent) {
13402    inheritsLoose(Button, _ClickableComponent);
13403
13404    function Button() {
13405      return _ClickableComponent.apply(this, arguments) || this;
13406    }
13407
13408    var _proto = Button.prototype;
13409
13410    /**
13411     * Create the `Button`s DOM element.
13412     *
13413     * @param {string} [tag="button"]
13414     *        The element's node type. This argument is IGNORED: no matter what
13415     *        is passed, it will always create a `button` element.
13416     *
13417     * @param {Object} [props={}]
13418     *        An object of properties that should be set on the element.
13419     *
13420     * @param {Object} [attributes={}]
13421     *        An object of attributes that should be set on the element.
13422     *
13423     * @return {Element}
13424     *         The element that gets created.
13425     */
13426    _proto.createEl = function createEl(tag, props, attributes) {
13427      if (props === void 0) {
13428        props = {};
13429      }
13430
13431      if (attributes === void 0) {
13432        attributes = {};
13433      }
13434
13435      tag = 'button';
13436      props = assign({
13437        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
13438        className: this.buildCSSClass()
13439      }, props); // Add attributes for button element
13440
13441      attributes = assign({
13442        // Necessary since the default button type is "submit"
13443        type: 'button'
13444      }, attributes);
13445      var el = Component.prototype.createEl.call(this, tag, props, attributes);
13446      this.createControlTextEl(el);
13447      return el;
13448    }
13449    /**
13450     * Add a child `Component` inside of this `Button`.
13451     *
13452     * @param {string|Component} child
13453     *        The name or instance of a child to add.
13454     *
13455     * @param {Object} [options={}]
13456     *        The key/value store of options that will get passed to children of
13457     *        the child.
13458     *
13459     * @return {Component}
13460     *         The `Component` that gets added as a child. When using a string the
13461     *         `Component` will get created by this process.
13462     *
13463     * @deprecated since version 5
13464     */
13465    ;
13466
13467    _proto.addChild = function addChild(child, options) {
13468      if (options === void 0) {
13469        options = {};
13470      }
13471
13472      var className = this.constructor.name;
13473      log.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method
13474
13475      return Component.prototype.addChild.call(this, child, options);
13476    }
13477    /**
13478     * Enable the `Button` element so that it can be activated or clicked. Use this with
13479     * {@link Button#disable}.
13480     */
13481    ;
13482
13483    _proto.enable = function enable() {
13484      _ClickableComponent.prototype.enable.call(this);
13485
13486      this.el_.removeAttribute('disabled');
13487    }
13488    /**
13489     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
13490     * {@link Button#enable}.
13491     */
13492    ;
13493
13494    _proto.disable = function disable() {
13495      _ClickableComponent.prototype.disable.call(this);
13496
13497      this.el_.setAttribute('disabled', 'disabled');
13498    }
13499    /**
13500     * This gets called when a `Button` has focus and `keydown` is triggered via a key
13501     * press.
13502     *
13503     * @param {EventTarget~Event} event
13504     *        The event that caused this function to get called.
13505     *
13506     * @listens keydown
13507     */
13508    ;
13509
13510    _proto.handleKeyDown = function handleKeyDown(event) {
13511      // Ignore Space or Enter key operation, which is handled by the browser for
13512      // a button - though not for its super class, ClickableComponent. Also,
13513      // prevent the event from propagating through the DOM and triggering Player
13514      // hotkeys. We do not preventDefault here because we _want_ the browser to
13515      // handle it.
13516      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
13517        event.stopPropagation();
13518        return;
13519      } // Pass keypress handling up for unsupported keys
13520
13521
13522      _ClickableComponent.prototype.handleKeyDown.call(this, event);
13523    };
13524
13525    return Button;
13526  }(ClickableComponent);
13527
13528  Component.registerComponent('Button', Button);
13529
13530  /**
13531   * The initial play button that shows before the video has played. The hiding of the
13532   * `BigPlayButton` get done via CSS and `Player` states.
13533   *
13534   * @extends Button
13535   */
13536
13537  var BigPlayButton = /*#__PURE__*/function (_Button) {
13538    inheritsLoose(BigPlayButton, _Button);
13539
13540    function BigPlayButton(player, options) {
13541      var _this;
13542
13543      _this = _Button.call(this, player, options) || this;
13544      _this.mouseused_ = false;
13545
13546      _this.on('mousedown', _this.handleMouseDown);
13547
13548      return _this;
13549    }
13550    /**
13551     * Builds the default DOM `className`.
13552     *
13553     * @return {string}
13554     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
13555     */
13556
13557
13558    var _proto = BigPlayButton.prototype;
13559
13560    _proto.buildCSSClass = function buildCSSClass() {
13561      return 'vjs-big-play-button';
13562    }
13563    /**
13564     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
13565     * for more detailed information on what a click can be.
13566     *
13567     * @param {EventTarget~Event} event
13568     *        The `keydown`, `tap`, or `click` event that caused this function to be
13569     *        called.
13570     *
13571     * @listens tap
13572     * @listens click
13573     */
13574    ;
13575
13576    _proto.handleClick = function handleClick(event) {
13577      var playPromise = this.player_.play(); // exit early if clicked via the mouse
13578
13579      if (this.mouseused_ && event.clientX && event.clientY) {
13580        var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
13581        silencePromise(playPromise);
13582
13583        if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
13584        // calling .focus() on the video element causes the video to go black,
13585        // so we avoid it in that specific case
13586        !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
13587          this.player_.tech(true).focus();
13588        }
13589
13590        return;
13591      }
13592
13593      var cb = this.player_.getChild('controlBar');
13594      var playToggle = cb && cb.getChild('playToggle');
13595
13596      if (!playToggle) {
13597        this.player_.tech(true).focus();
13598        return;
13599      }
13600
13601      var playFocus = function playFocus() {
13602        return playToggle.focus();
13603      };
13604
13605      if (isPromise(playPromise)) {
13606        playPromise.then(playFocus, function () {});
13607      } else {
13608        this.setTimeout(playFocus, 1);
13609      }
13610    };
13611
13612    _proto.handleKeyDown = function handleKeyDown(event) {
13613      this.mouseused_ = false;
13614
13615      _Button.prototype.handleKeyDown.call(this, event);
13616    };
13617
13618    _proto.handleMouseDown = function handleMouseDown(event) {
13619      this.mouseused_ = true;
13620    };
13621
13622    return BigPlayButton;
13623  }(Button);
13624  /**
13625   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
13626   *
13627   * @type {string}
13628   * @private
13629   */
13630
13631
13632  BigPlayButton.prototype.controlText_ = 'Play Video';
13633  Component.registerComponent('BigPlayButton', BigPlayButton);
13634
13635  /**
13636   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
13637   * it gets clicked.
13638   *
13639   * @extends Button
13640   */
13641
13642  var CloseButton = /*#__PURE__*/function (_Button) {
13643    inheritsLoose(CloseButton, _Button);
13644
13645    /**
13646    * Creates an instance of the this class.
13647    *
13648    * @param  {Player} player
13649    *         The `Player` that this class should be attached to.
13650    *
13651    * @param  {Object} [options]
13652    *         The key/value store of player options.
13653    */
13654    function CloseButton(player, options) {
13655      var _this;
13656
13657      _this = _Button.call(this, player, options) || this;
13658
13659      _this.controlText(options && options.controlText || _this.localize('Close'));
13660
13661      return _this;
13662    }
13663    /**
13664    * Builds the default DOM `className`.
13665    *
13666    * @return {string}
13667    *         The DOM `className` for this object.
13668    */
13669
13670
13671    var _proto = CloseButton.prototype;
13672
13673    _proto.buildCSSClass = function buildCSSClass() {
13674      return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
13675    }
13676    /**
13677     * This gets called when a `CloseButton` gets clicked. See
13678     * {@link ClickableComponent#handleClick} for more information on when
13679     * this will be triggered
13680     *
13681     * @param {EventTarget~Event} event
13682     *        The `keydown`, `tap`, or `click` event that caused this function to be
13683     *        called.
13684     *
13685     * @listens tap
13686     * @listens click
13687     * @fires CloseButton#close
13688     */
13689    ;
13690
13691    _proto.handleClick = function handleClick(event) {
13692      /**
13693       * Triggered when the a `CloseButton` is clicked.
13694       *
13695       * @event CloseButton#close
13696       * @type {EventTarget~Event}
13697       *
13698       * @property {boolean} [bubbles=false]
13699       *           set to false so that the close event does not
13700       *           bubble up to parents if there is no listener
13701       */
13702      this.trigger({
13703        type: 'close',
13704        bubbles: false
13705      });
13706    }
13707    /**
13708     * Event handler that is called when a `CloseButton` receives a
13709     * `keydown` event.
13710     *
13711     * By default, if the key is Esc, it will trigger a `click` event.
13712     *
13713     * @param {EventTarget~Event} event
13714     *        The `keydown` event that caused this function to be called.
13715     *
13716     * @listens keydown
13717     */
13718    ;
13719
13720    _proto.handleKeyDown = function handleKeyDown(event) {
13721      // Esc button will trigger `click` event
13722      if (keycode.isEventKey(event, 'Esc')) {
13723        event.preventDefault();
13724        event.stopPropagation();
13725        this.trigger('click');
13726      } else {
13727        // Pass keypress handling up for unsupported keys
13728        _Button.prototype.handleKeyDown.call(this, event);
13729      }
13730    };
13731
13732    return CloseButton;
13733  }(Button);
13734
13735  Component.registerComponent('CloseButton', CloseButton);
13736
13737  /**
13738   * Button to toggle between play and pause.
13739   *
13740   * @extends Button
13741   */
13742
13743  var PlayToggle = /*#__PURE__*/function (_Button) {
13744    inheritsLoose(PlayToggle, _Button);
13745
13746    /**
13747     * Creates an instance of this class.
13748     *
13749     * @param {Player} player
13750     *        The `Player` that this class should be attached to.
13751     *
13752     * @param {Object} [options={}]
13753     *        The key/value store of player options.
13754     */
13755    function PlayToggle(player, options) {
13756      var _this;
13757
13758      if (options === void 0) {
13759        options = {};
13760      }
13761
13762      _this = _Button.call(this, player, options) || this; // show or hide replay icon
13763
13764      options.replay = options.replay === undefined || options.replay;
13765
13766      _this.on(player, 'play', _this.handlePlay);
13767
13768      _this.on(player, 'pause', _this.handlePause);
13769
13770      if (options.replay) {
13771        _this.on(player, 'ended', _this.handleEnded);
13772      }
13773
13774      return _this;
13775    }
13776    /**
13777     * Builds the default DOM `className`.
13778     *
13779     * @return {string}
13780     *         The DOM `className` for this object.
13781     */
13782
13783
13784    var _proto = PlayToggle.prototype;
13785
13786    _proto.buildCSSClass = function buildCSSClass() {
13787      return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
13788    }
13789    /**
13790     * This gets called when an `PlayToggle` is "clicked". See
13791     * {@link ClickableComponent} for more detailed information on what a click can be.
13792     *
13793     * @param {EventTarget~Event} [event]
13794     *        The `keydown`, `tap`, or `click` event that caused this function to be
13795     *        called.
13796     *
13797     * @listens tap
13798     * @listens click
13799     */
13800    ;
13801
13802    _proto.handleClick = function handleClick(event) {
13803      if (this.player_.paused()) {
13804        this.player_.play();
13805      } else {
13806        this.player_.pause();
13807      }
13808    }
13809    /**
13810     * This gets called once after the video has ended and the user seeks so that
13811     * we can change the replay button back to a play button.
13812     *
13813     * @param {EventTarget~Event} [event]
13814     *        The event that caused this function to run.
13815     *
13816     * @listens Player#seeked
13817     */
13818    ;
13819
13820    _proto.handleSeeked = function handleSeeked(event) {
13821      this.removeClass('vjs-ended');
13822
13823      if (this.player_.paused()) {
13824        this.handlePause(event);
13825      } else {
13826        this.handlePlay(event);
13827      }
13828    }
13829    /**
13830     * Add the vjs-playing class to the element so it can change appearance.
13831     *
13832     * @param {EventTarget~Event} [event]
13833     *        The event that caused this function to run.
13834     *
13835     * @listens Player#play
13836     */
13837    ;
13838
13839    _proto.handlePlay = function handlePlay(event) {
13840      this.removeClass('vjs-ended');
13841      this.removeClass('vjs-paused');
13842      this.addClass('vjs-playing'); // change the button text to "Pause"
13843
13844      this.controlText('Pause');
13845    }
13846    /**
13847     * Add the vjs-paused class to the element so it can change appearance.
13848     *
13849     * @param {EventTarget~Event} [event]
13850     *        The event that caused this function to run.
13851     *
13852     * @listens Player#pause
13853     */
13854    ;
13855
13856    _proto.handlePause = function handlePause(event) {
13857      this.removeClass('vjs-playing');
13858      this.addClass('vjs-paused'); // change the button text to "Play"
13859
13860      this.controlText('Play');
13861    }
13862    /**
13863     * Add the vjs-ended class to the element so it can change appearance
13864     *
13865     * @param {EventTarget~Event} [event]
13866     *        The event that caused this function to run.
13867     *
13868     * @listens Player#ended
13869     */
13870    ;
13871
13872    _proto.handleEnded = function handleEnded(event) {
13873      this.removeClass('vjs-playing');
13874      this.addClass('vjs-ended'); // change the button text to "Replay"
13875
13876      this.controlText('Replay'); // on the next seek remove the replay button
13877
13878      this.one(this.player_, 'seeked', this.handleSeeked);
13879    };
13880
13881    return PlayToggle;
13882  }(Button);
13883  /**
13884   * The text that should display over the `PlayToggle`s controls. Added for localization.
13885   *
13886   * @type {string}
13887   * @private
13888   */
13889
13890
13891  PlayToggle.prototype.controlText_ = 'Play';
13892  Component.registerComponent('PlayToggle', PlayToggle);
13893
13894  /**
13895   * @file format-time.js
13896   * @module format-time
13897   */
13898
13899  /**
13900   * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
13901   * seconds) will force a number of leading zeros to cover the length of the
13902   * guide.
13903   *
13904   * @private
13905   * @param  {number} seconds
13906   *         Number of seconds to be turned into a string
13907   *
13908   * @param  {number} guide
13909   *         Number (in seconds) to model the string after
13910   *
13911   * @return {string}
13912   *         Time formatted as H:MM:SS or M:SS
13913   */
13914  var defaultImplementation = function defaultImplementation(seconds, guide) {
13915    seconds = seconds < 0 ? 0 : seconds;
13916    var s = Math.floor(seconds % 60);
13917    var m = Math.floor(seconds / 60 % 60);
13918    var h = Math.floor(seconds / 3600);
13919    var gm = Math.floor(guide / 60 % 60);
13920    var gh = Math.floor(guide / 3600); // handle invalid times
13921
13922    if (isNaN(seconds) || seconds === Infinity) {
13923      // '-' is false for all relational operators (e.g. <, >=) so this setting
13924      // will add the minimum number of fields specified by the guide
13925      h = m = s = '-';
13926    } // Check if we need to show hours
13927
13928
13929    h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
13930    // Always show at least one digit of minutes.
13931
13932    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
13933
13934    s = s < 10 ? '0' + s : s;
13935    return h + m + s;
13936  }; // Internal pointer to the current implementation.
13937
13938
13939  var implementation = defaultImplementation;
13940  /**
13941   * Replaces the default formatTime implementation with a custom implementation.
13942   *
13943   * @param {Function} customImplementation
13944   *        A function which will be used in place of the default formatTime
13945   *        implementation. Will receive the current time in seconds and the
13946   *        guide (in seconds) as arguments.
13947   */
13948
13949  function setFormatTime(customImplementation) {
13950    implementation = customImplementation;
13951  }
13952  /**
13953   * Resets formatTime to the default implementation.
13954   */
13955
13956  function resetFormatTime() {
13957    implementation = defaultImplementation;
13958  }
13959  /**
13960   * Delegates to either the default time formatting function or a custom
13961   * function supplied via `setFormatTime`.
13962   *
13963   * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
13964   * guide (in seconds) will force a number of leading zeros to cover the
13965   * length of the guide.
13966   *
13967   * @static
13968   * @example  formatTime(125, 600) === "02:05"
13969   * @param    {number} seconds
13970   *           Number of seconds to be turned into a string
13971   *
13972   * @param    {number} guide
13973   *           Number (in seconds) to model the string after
13974   *
13975   * @return   {string}
13976   *           Time formatted as H:MM:SS or M:SS
13977   */
13978
13979  function formatTime(seconds, guide) {
13980    if (guide === void 0) {
13981      guide = seconds;
13982    }
13983
13984    return implementation(seconds, guide);
13985  }
13986
13987  /**
13988   * Displays time information about the video
13989   *
13990   * @extends Component
13991   */
13992
13993  var TimeDisplay = /*#__PURE__*/function (_Component) {
13994    inheritsLoose(TimeDisplay, _Component);
13995
13996    /**
13997     * Creates an instance of this class.
13998     *
13999     * @param {Player} player
14000     *        The `Player` that this class should be attached to.
14001     *
14002     * @param {Object} [options]
14003     *        The key/value store of player options.
14004     */
14005    function TimeDisplay(player, options) {
14006      var _this;
14007
14008      _this = _Component.call(this, player, options) || this;
14009
14010      _this.on(player, ['timeupdate', 'ended'], _this.updateContent);
14011
14012      _this.updateTextNode_();
14013
14014      return _this;
14015    }
14016    /**
14017     * Create the `Component`'s DOM element
14018     *
14019     * @return {Element}
14020     *         The element that was created.
14021     */
14022
14023
14024    var _proto = TimeDisplay.prototype;
14025
14026    _proto.createEl = function createEl$1() {
14027      var className = this.buildCSSClass();
14028
14029      var el = _Component.prototype.createEl.call(this, 'div', {
14030        className: className + " vjs-time-control vjs-control",
14031        innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
14032      });
14033
14034      this.contentEl_ = createEl('span', {
14035        className: className + "-display"
14036      }, {
14037        // tell screen readers not to automatically read the time as it changes
14038        'aria-live': 'off',
14039        // span elements have no implicit role, but some screen readers (notably VoiceOver)
14040        // treat them as a break between items in the DOM when using arrow keys
14041        // (or left-to-right swipes on iOS) to read contents of a page. Using
14042        // role='presentation' causes VoiceOver to NOT treat this span as a break.
14043        'role': 'presentation'
14044      });
14045      el.appendChild(this.contentEl_);
14046      return el;
14047    };
14048
14049    _proto.dispose = function dispose() {
14050      this.contentEl_ = null;
14051      this.textNode_ = null;
14052
14053      _Component.prototype.dispose.call(this);
14054    }
14055    /**
14056     * Updates the time display text node with a new time
14057     *
14058     * @param {number} [time=0] the time to update to
14059     *
14060     * @private
14061     */
14062    ;
14063
14064    _proto.updateTextNode_ = function updateTextNode_(time) {
14065      var _this2 = this;
14066
14067      if (time === void 0) {
14068        time = 0;
14069      }
14070
14071      time = formatTime(time);
14072
14073      if (this.formattedTime_ === time) {
14074        return;
14075      }
14076
14077      this.formattedTime_ = time;
14078      this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', function () {
14079        if (!_this2.contentEl_) {
14080          return;
14081        }
14082
14083        var oldNode = _this2.textNode_;
14084        _this2.textNode_ = document.createTextNode(_this2.formattedTime_);
14085
14086        if (!_this2.textNode_) {
14087          return;
14088        }
14089
14090        if (oldNode) {
14091          _this2.contentEl_.replaceChild(_this2.textNode_, oldNode);
14092        } else {
14093          _this2.contentEl_.appendChild(_this2.textNode_);
14094        }
14095      });
14096    }
14097    /**
14098     * To be filled out in the child class, should update the displayed time
14099     * in accordance with the fact that the current time has changed.
14100     *
14101     * @param {EventTarget~Event} [event]
14102     *        The `timeupdate`  event that caused this to run.
14103     *
14104     * @listens Player#timeupdate
14105     */
14106    ;
14107
14108    _proto.updateContent = function updateContent(event) {};
14109
14110    return TimeDisplay;
14111  }(Component);
14112  /**
14113   * The text that is added to the `TimeDisplay` for screen reader users.
14114   *
14115   * @type {string}
14116   * @private
14117   */
14118
14119
14120  TimeDisplay.prototype.labelText_ = 'Time';
14121  /**
14122   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
14123   *
14124   * @type {string}
14125   * @private
14126   *
14127   * @deprecated in v7; controlText_ is not used in non-active display Components
14128   */
14129
14130  TimeDisplay.prototype.controlText_ = 'Time';
14131  Component.registerComponent('TimeDisplay', TimeDisplay);
14132
14133  /**
14134   * Displays the current time
14135   *
14136   * @extends Component
14137   */
14138
14139  var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
14140    inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
14141
14142    function CurrentTimeDisplay() {
14143      return _TimeDisplay.apply(this, arguments) || this;
14144    }
14145
14146    var _proto = CurrentTimeDisplay.prototype;
14147
14148    /**
14149     * Builds the default DOM `className`.
14150     *
14151     * @return {string}
14152     *         The DOM `className` for this object.
14153     */
14154    _proto.buildCSSClass = function buildCSSClass() {
14155      return 'vjs-current-time';
14156    }
14157    /**
14158     * Update current time display
14159     *
14160     * @param {EventTarget~Event} [event]
14161     *        The `timeupdate` event that caused this function to run.
14162     *
14163     * @listens Player#timeupdate
14164     */
14165    ;
14166
14167    _proto.updateContent = function updateContent(event) {
14168      // Allows for smooth scrubbing, when player can't keep up.
14169      var time;
14170
14171      if (this.player_.ended()) {
14172        time = this.player_.duration();
14173      } else {
14174        time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14175      }
14176
14177      this.updateTextNode_(time);
14178    };
14179
14180    return CurrentTimeDisplay;
14181  }(TimeDisplay);
14182  /**
14183   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
14184   *
14185   * @type {string}
14186   * @private
14187   */
14188
14189
14190  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
14191  /**
14192   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
14193   *
14194   * @type {string}
14195   * @private
14196   *
14197   * @deprecated in v7; controlText_ is not used in non-active display Components
14198   */
14199
14200  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
14201  Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
14202
14203  /**
14204   * Displays the duration
14205   *
14206   * @extends Component
14207   */
14208
14209  var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) {
14210    inheritsLoose(DurationDisplay, _TimeDisplay);
14211
14212    /**
14213     * Creates an instance of this class.
14214     *
14215     * @param {Player} player
14216     *        The `Player` that this class should be attached to.
14217     *
14218     * @param {Object} [options]
14219     *        The key/value store of player options.
14220     */
14221    function DurationDisplay(player, options) {
14222      var _this;
14223
14224      _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
14225      // as they should always display the changed duration as
14226      // it has changed
14227
14228      _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
14229      // but the durationchange on the user agent will not fire.
14230      // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
14231
14232
14233      _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
14234      // listeners could have broken dependent applications/libraries. These
14235      // can likely be removed for 7.0.
14236
14237
14238      _this.on(player, 'loadedmetadata', _this.updateContent);
14239
14240      return _this;
14241    }
14242    /**
14243     * Builds the default DOM `className`.
14244     *
14245     * @return {string}
14246     *         The DOM `className` for this object.
14247     */
14248
14249
14250    var _proto = DurationDisplay.prototype;
14251
14252    _proto.buildCSSClass = function buildCSSClass() {
14253      return 'vjs-duration';
14254    }
14255    /**
14256     * Update duration time display.
14257     *
14258     * @param {EventTarget~Event} [event]
14259     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
14260     *        this function to be called.
14261     *
14262     * @listens Player#durationchange
14263     * @listens Player#timeupdate
14264     * @listens Player#loadedmetadata
14265     */
14266    ;
14267
14268    _proto.updateContent = function updateContent(event) {
14269      var duration = this.player_.duration();
14270      this.updateTextNode_(duration);
14271    };
14272
14273    return DurationDisplay;
14274  }(TimeDisplay);
14275  /**
14276   * The text that is added to the `DurationDisplay` for screen reader users.
14277   *
14278   * @type {string}
14279   * @private
14280   */
14281
14282
14283  DurationDisplay.prototype.labelText_ = 'Duration';
14284  /**
14285   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
14286   *
14287   * @type {string}
14288   * @private
14289   *
14290   * @deprecated in v7; controlText_ is not used in non-active display Components
14291   */
14292
14293  DurationDisplay.prototype.controlText_ = 'Duration';
14294  Component.registerComponent('DurationDisplay', DurationDisplay);
14295
14296  /**
14297   * The separator between the current time and duration.
14298   * Can be hidden if it's not needed in the design.
14299   *
14300   * @extends Component
14301   */
14302
14303  var TimeDivider = /*#__PURE__*/function (_Component) {
14304    inheritsLoose(TimeDivider, _Component);
14305
14306    function TimeDivider() {
14307      return _Component.apply(this, arguments) || this;
14308    }
14309
14310    var _proto = TimeDivider.prototype;
14311
14312    /**
14313     * Create the component's DOM element
14314     *
14315     * @return {Element}
14316     *         The element that was created.
14317     */
14318    _proto.createEl = function createEl() {
14319      return _Component.prototype.createEl.call(this, 'div', {
14320        className: 'vjs-time-control vjs-time-divider',
14321        innerHTML: '<div><span>/</span></div>'
14322      }, {
14323        // this element and its contents can be hidden from assistive techs since
14324        // it is made extraneous by the announcement of the control text
14325        // for the current time and duration displays
14326        'aria-hidden': true
14327      });
14328    };
14329
14330    return TimeDivider;
14331  }(Component);
14332
14333  Component.registerComponent('TimeDivider', TimeDivider);
14334
14335  /**
14336   * Displays the time left in the video
14337   *
14338   * @extends Component
14339   */
14340
14341  var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
14342    inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
14343
14344    /**
14345     * Creates an instance of this class.
14346     *
14347     * @param {Player} player
14348     *        The `Player` that this class should be attached to.
14349     *
14350     * @param {Object} [options]
14351     *        The key/value store of player options.
14352     */
14353    function RemainingTimeDisplay(player, options) {
14354      var _this;
14355
14356      _this = _TimeDisplay.call(this, player, options) || this;
14357
14358      _this.on(player, 'durationchange', _this.updateContent);
14359
14360      return _this;
14361    }
14362    /**
14363     * Builds the default DOM `className`.
14364     *
14365     * @return {string}
14366     *         The DOM `className` for this object.
14367     */
14368
14369
14370    var _proto = RemainingTimeDisplay.prototype;
14371
14372    _proto.buildCSSClass = function buildCSSClass() {
14373      return 'vjs-remaining-time';
14374    }
14375    /**
14376     * Create the `Component`'s DOM element with the "minus" characted prepend to the time
14377     *
14378     * @return {Element}
14379     *         The element that was created.
14380     */
14381    ;
14382
14383    _proto.createEl = function createEl$1() {
14384      var el = _TimeDisplay.prototype.createEl.call(this);
14385
14386      el.insertBefore(createEl('span', {}, {
14387        'aria-hidden': true
14388      }, '-'), this.contentEl_);
14389      return el;
14390    }
14391    /**
14392     * Update remaining time display.
14393     *
14394     * @param {EventTarget~Event} [event]
14395     *        The `timeupdate` or `durationchange` event that caused this to run.
14396     *
14397     * @listens Player#timeupdate
14398     * @listens Player#durationchange
14399     */
14400    ;
14401
14402    _proto.updateContent = function updateContent(event) {
14403      if (typeof this.player_.duration() !== 'number') {
14404        return;
14405      }
14406
14407      var time; // @deprecated We should only use remainingTimeDisplay
14408      // as of video.js 7
14409
14410      if (this.player_.ended()) {
14411        time = 0;
14412      } else if (this.player_.remainingTimeDisplay) {
14413        time = this.player_.remainingTimeDisplay();
14414      } else {
14415        time = this.player_.remainingTime();
14416      }
14417
14418      this.updateTextNode_(time);
14419    };
14420
14421    return RemainingTimeDisplay;
14422  }(TimeDisplay);
14423  /**
14424   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
14425   *
14426   * @type {string}
14427   * @private
14428   */
14429
14430
14431  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
14432  /**
14433   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
14434   *
14435   * @type {string}
14436   * @private
14437   *
14438   * @deprecated in v7; controlText_ is not used in non-active display Components
14439   */
14440
14441  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
14442  Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
14443
14444  /**
14445   * Displays the live indicator when duration is Infinity.
14446   *
14447   * @extends Component
14448   */
14449
14450  var LiveDisplay = /*#__PURE__*/function (_Component) {
14451    inheritsLoose(LiveDisplay, _Component);
14452
14453    /**
14454     * Creates an instance of this class.
14455     *
14456     * @param {Player} player
14457     *        The `Player` that this class should be attached to.
14458     *
14459     * @param {Object} [options]
14460     *        The key/value store of player options.
14461     */
14462    function LiveDisplay(player, options) {
14463      var _this;
14464
14465      _this = _Component.call(this, player, options) || this;
14466
14467      _this.updateShowing();
14468
14469      _this.on(_this.player(), 'durationchange', _this.updateShowing);
14470
14471      return _this;
14472    }
14473    /**
14474     * Create the `Component`'s DOM element
14475     *
14476     * @return {Element}
14477     *         The element that was created.
14478     */
14479
14480
14481    var _proto = LiveDisplay.prototype;
14482
14483    _proto.createEl = function createEl$1() {
14484      var el = _Component.prototype.createEl.call(this, 'div', {
14485        className: 'vjs-live-control vjs-control'
14486      });
14487
14488      this.contentEl_ = createEl('div', {
14489        className: 'vjs-live-display',
14490        innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
14491      }, {
14492        'aria-live': 'off'
14493      });
14494      el.appendChild(this.contentEl_);
14495      return el;
14496    };
14497
14498    _proto.dispose = function dispose() {
14499      this.contentEl_ = null;
14500
14501      _Component.prototype.dispose.call(this);
14502    }
14503    /**
14504     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
14505     * it accordingly
14506     *
14507     * @param {EventTarget~Event} [event]
14508     *        The {@link Player#durationchange} event that caused this function to run.
14509     *
14510     * @listens Player#durationchange
14511     */
14512    ;
14513
14514    _proto.updateShowing = function updateShowing(event) {
14515      if (this.player().duration() === Infinity) {
14516        this.show();
14517      } else {
14518        this.hide();
14519      }
14520    };
14521
14522    return LiveDisplay;
14523  }(Component);
14524
14525  Component.registerComponent('LiveDisplay', LiveDisplay);
14526
14527  /**
14528   * Displays the live indicator when duration is Infinity.
14529   *
14530   * @extends Component
14531   */
14532
14533  var SeekToLive = /*#__PURE__*/function (_Button) {
14534    inheritsLoose(SeekToLive, _Button);
14535
14536    /**
14537     * Creates an instance of this class.
14538     *
14539     * @param {Player} player
14540     *        The `Player` that this class should be attached to.
14541     *
14542     * @param {Object} [options]
14543     *        The key/value store of player options.
14544     */
14545    function SeekToLive(player, options) {
14546      var _this;
14547
14548      _this = _Button.call(this, player, options) || this;
14549
14550      _this.updateLiveEdgeStatus();
14551
14552      if (_this.player_.liveTracker) {
14553        _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
14554      }
14555
14556      return _this;
14557    }
14558    /**
14559     * Create the `Component`'s DOM element
14560     *
14561     * @return {Element}
14562     *         The element that was created.
14563     */
14564
14565
14566    var _proto = SeekToLive.prototype;
14567
14568    _proto.createEl = function createEl$1() {
14569      var el = _Button.prototype.createEl.call(this, 'button', {
14570        className: 'vjs-seek-to-live-control vjs-control'
14571      });
14572
14573      this.textEl_ = createEl('span', {
14574        className: 'vjs-seek-to-live-text',
14575        innerHTML: this.localize('LIVE')
14576      }, {
14577        'aria-hidden': 'true'
14578      });
14579      el.appendChild(this.textEl_);
14580      return el;
14581    }
14582    /**
14583     * Update the state of this button if we are at the live edge
14584     * or not
14585     */
14586    ;
14587
14588    _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() {
14589      // default to live edge
14590      if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
14591        this.setAttribute('aria-disabled', true);
14592        this.addClass('vjs-at-live-edge');
14593        this.controlText('Seek to live, currently playing live');
14594      } else {
14595        this.setAttribute('aria-disabled', false);
14596        this.removeClass('vjs-at-live-edge');
14597        this.controlText('Seek to live, currently behind live');
14598      }
14599    }
14600    /**
14601     * On click bring us as near to the live point as possible.
14602     * This requires that we wait for the next `live-seekable-change`
14603     * event which will happen every segment length seconds.
14604     */
14605    ;
14606
14607    _proto.handleClick = function handleClick() {
14608      this.player_.liveTracker.seekToLiveEdge();
14609    }
14610    /**
14611     * Dispose of the element and stop tracking
14612     */
14613    ;
14614
14615    _proto.dispose = function dispose() {
14616      if (this.player_.liveTracker) {
14617        this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
14618      }
14619
14620      this.textEl_ = null;
14621
14622      _Button.prototype.dispose.call(this);
14623    };
14624
14625    return SeekToLive;
14626  }(Button);
14627
14628  SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
14629  Component.registerComponent('SeekToLive', SeekToLive);
14630
14631  /**
14632   * Keep a number between a min and a max value
14633   *
14634   * @param {number} number
14635   *        The number to clamp
14636   *
14637   * @param {number} min
14638   *        The minimum value
14639   * @param {number} max
14640   *        The maximum value
14641   *
14642   * @return {number}
14643   *         the clamped number
14644   */
14645  var clamp = function clamp(number, min, max) {
14646    number = Number(number);
14647    return Math.min(max, Math.max(min, isNaN(number) ? min : number));
14648  };
14649
14650  /**
14651   * The base functionality for a slider. Can be vertical or horizontal.
14652   * For instance the volume bar or the seek bar on a video is a slider.
14653   *
14654   * @extends Component
14655   */
14656
14657  var Slider = /*#__PURE__*/function (_Component) {
14658    inheritsLoose(Slider, _Component);
14659
14660    /**
14661    * Create an instance of this class
14662    *
14663    * @param {Player} player
14664    *        The `Player` that this class should be attached to.
14665    *
14666    * @param {Object} [options]
14667    *        The key/value store of player options.
14668    */
14669    function Slider(player, options) {
14670      var _this;
14671
14672      _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
14673
14674      _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
14675
14676      _this.vertical(!!_this.options_.vertical);
14677
14678      _this.enable();
14679
14680      return _this;
14681    }
14682    /**
14683     * Are controls are currently enabled for this slider or not.
14684     *
14685     * @return {boolean}
14686     *         true if controls are enabled, false otherwise
14687     */
14688
14689
14690    var _proto = Slider.prototype;
14691
14692    _proto.enabled = function enabled() {
14693      return this.enabled_;
14694    }
14695    /**
14696     * Enable controls for this slider if they are disabled
14697     */
14698    ;
14699
14700    _proto.enable = function enable() {
14701      if (this.enabled()) {
14702        return;
14703      }
14704
14705      this.on('mousedown', this.handleMouseDown);
14706      this.on('touchstart', this.handleMouseDown);
14707      this.on('keydown', this.handleKeyDown);
14708      this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired
14709
14710      this.on(this.player_, 'controlsvisible', this.update);
14711
14712      if (this.playerEvent) {
14713        this.on(this.player_, this.playerEvent, this.update);
14714      }
14715
14716      this.removeClass('disabled');
14717      this.setAttribute('tabindex', 0);
14718      this.enabled_ = true;
14719    }
14720    /**
14721     * Disable controls for this slider if they are enabled
14722     */
14723    ;
14724
14725    _proto.disable = function disable() {
14726      if (!this.enabled()) {
14727        return;
14728      }
14729
14730      var doc = this.bar.el_.ownerDocument;
14731      this.off('mousedown', this.handleMouseDown);
14732      this.off('touchstart', this.handleMouseDown);
14733      this.off('keydown', this.handleKeyDown);
14734      this.off('click', this.handleClick);
14735      this.off(this.player_, 'controlsvisible', this.update);
14736      this.off(doc, 'mousemove', this.handleMouseMove);
14737      this.off(doc, 'mouseup', this.handleMouseUp);
14738      this.off(doc, 'touchmove', this.handleMouseMove);
14739      this.off(doc, 'touchend', this.handleMouseUp);
14740      this.removeAttribute('tabindex');
14741      this.addClass('disabled');
14742
14743      if (this.playerEvent) {
14744        this.off(this.player_, this.playerEvent, this.update);
14745      }
14746
14747      this.enabled_ = false;
14748    }
14749    /**
14750     * Create the `Slider`s DOM element.
14751     *
14752     * @param {string} type
14753     *        Type of element to create.
14754     *
14755     * @param {Object} [props={}]
14756     *        List of properties in Object form.
14757     *
14758     * @param {Object} [attributes={}]
14759     *        list of attributes in Object form.
14760     *
14761     * @return {Element}
14762     *         The element that gets created.
14763     */
14764    ;
14765
14766    _proto.createEl = function createEl(type, props, attributes) {
14767      if (props === void 0) {
14768        props = {};
14769      }
14770
14771      if (attributes === void 0) {
14772        attributes = {};
14773      }
14774
14775      // Add the slider element class to all sub classes
14776      props.className = props.className + ' vjs-slider';
14777      props = assign({
14778        tabIndex: 0
14779      }, props);
14780      attributes = assign({
14781        'role': 'slider',
14782        'aria-valuenow': 0,
14783        'aria-valuemin': 0,
14784        'aria-valuemax': 100,
14785        'tabIndex': 0
14786      }, attributes);
14787      return _Component.prototype.createEl.call(this, type, props, attributes);
14788    }
14789    /**
14790     * Handle `mousedown` or `touchstart` events on the `Slider`.
14791     *
14792     * @param {EventTarget~Event} event
14793     *        `mousedown` or `touchstart` event that triggered this function
14794     *
14795     * @listens mousedown
14796     * @listens touchstart
14797     * @fires Slider#slideractive
14798     */
14799    ;
14800
14801    _proto.handleMouseDown = function handleMouseDown(event) {
14802      var doc = this.bar.el_.ownerDocument;
14803
14804      if (event.type === 'mousedown') {
14805        event.preventDefault();
14806      } // Do not call preventDefault() on touchstart in Chrome
14807      // to avoid console warnings. Use a 'touch-action: none' style
14808      // instead to prevent unintented scrolling.
14809      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
14810
14811
14812      if (event.type === 'touchstart' && !IS_CHROME) {
14813        event.preventDefault();
14814      }
14815
14816      blockTextSelection();
14817      this.addClass('vjs-sliding');
14818      /**
14819       * Triggered when the slider is in an active state
14820       *
14821       * @event Slider#slideractive
14822       * @type {EventTarget~Event}
14823       */
14824
14825      this.trigger('slideractive');
14826      this.on(doc, 'mousemove', this.handleMouseMove);
14827      this.on(doc, 'mouseup', this.handleMouseUp);
14828      this.on(doc, 'touchmove', this.handleMouseMove);
14829      this.on(doc, 'touchend', this.handleMouseUp);
14830      this.handleMouseMove(event);
14831    }
14832    /**
14833     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
14834     * The `mousemove` and `touchmove` events will only only trigger this function during
14835     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
14836     * {@link Slider#handleMouseUp}.
14837     *
14838     * @param {EventTarget~Event} event
14839     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
14840     *        this function
14841     *
14842     * @listens mousemove
14843     * @listens touchmove
14844     */
14845    ;
14846
14847    _proto.handleMouseMove = function handleMouseMove(event) {}
14848    /**
14849     * Handle `mouseup` or `touchend` events on the `Slider`.
14850     *
14851     * @param {EventTarget~Event} event
14852     *        `mouseup` or `touchend` event that triggered this function.
14853     *
14854     * @listens touchend
14855     * @listens mouseup
14856     * @fires Slider#sliderinactive
14857     */
14858    ;
14859
14860    _proto.handleMouseUp = function handleMouseUp() {
14861      var doc = this.bar.el_.ownerDocument;
14862      unblockTextSelection();
14863      this.removeClass('vjs-sliding');
14864      /**
14865       * Triggered when the slider is no longer in an active state.
14866       *
14867       * @event Slider#sliderinactive
14868       * @type {EventTarget~Event}
14869       */
14870
14871      this.trigger('sliderinactive');
14872      this.off(doc, 'mousemove', this.handleMouseMove);
14873      this.off(doc, 'mouseup', this.handleMouseUp);
14874      this.off(doc, 'touchmove', this.handleMouseMove);
14875      this.off(doc, 'touchend', this.handleMouseUp);
14876      this.update();
14877    }
14878    /**
14879     * Update the progress bar of the `Slider`.
14880     *
14881     * @return {number}
14882     *          The percentage of progress the progress bar represents as a
14883     *          number from 0 to 1.
14884     */
14885    ;
14886
14887    _proto.update = function update() {
14888      var _this2 = this;
14889
14890      // In VolumeBar init we have a setTimeout for update that pops and update
14891      // to the end of the execution stack. The player is destroyed before then
14892      // update will cause an error
14893      // If there's no bar...
14894      if (!this.el_ || !this.bar) {
14895        return;
14896      } // clamp progress between 0 and 1
14897      // and only round to four decimal places, as we round to two below
14898
14899
14900      var progress = this.getProgress();
14901
14902      if (progress === this.progress_) {
14903        return progress;
14904      }
14905
14906      this.progress_ = progress;
14907      this.requestNamedAnimationFrame('Slider#update', function () {
14908        // Set the new bar width or height
14909        var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value
14910
14911        _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
14912      });
14913      return progress;
14914    }
14915    /**
14916     * Get the percentage of the bar that should be filled
14917     * but clamped and rounded.
14918     *
14919     * @return {number}
14920     *         percentage filled that the slider is
14921     */
14922    ;
14923
14924    _proto.getProgress = function getProgress() {
14925      return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
14926    }
14927    /**
14928     * Calculate distance for slider
14929     *
14930     * @param {EventTarget~Event} event
14931     *        The event that caused this function to run.
14932     *
14933     * @return {number}
14934     *         The current position of the Slider.
14935     *         - position.x for vertical `Slider`s
14936     *         - position.y for horizontal `Slider`s
14937     */
14938    ;
14939
14940    _proto.calculateDistance = function calculateDistance(event) {
14941      var position = getPointerPosition(this.el_, event);
14942
14943      if (this.vertical()) {
14944        return position.y;
14945      }
14946
14947      return position.x;
14948    }
14949    /**
14950     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
14951     * arrow keys. This function will only be called when the slider has focus. See
14952     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
14953     *
14954     * @param {EventTarget~Event} event
14955     *        the `keydown` event that caused this function to run.
14956     *
14957     * @listens keydown
14958     */
14959    ;
14960
14961    _proto.handleKeyDown = function handleKeyDown(event) {
14962      // Left and Down Arrows
14963      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
14964        event.preventDefault();
14965        event.stopPropagation();
14966        this.stepBack(); // Up and Right Arrows
14967      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
14968        event.preventDefault();
14969        event.stopPropagation();
14970        this.stepForward();
14971      } else {
14972        // Pass keydown handling up for unsupported keys
14973        _Component.prototype.handleKeyDown.call(this, event);
14974      }
14975    }
14976    /**
14977     * Listener for click events on slider, used to prevent clicks
14978     *   from bubbling up to parent elements like button menus.
14979     *
14980     * @param {Object} event
14981     *        Event that caused this object to run
14982     */
14983    ;
14984
14985    _proto.handleClick = function handleClick(event) {
14986      event.stopPropagation();
14987      event.preventDefault();
14988    }
14989    /**
14990     * Get/set if slider is horizontal for vertical
14991     *
14992     * @param {boolean} [bool]
14993     *        - true if slider is vertical,
14994     *        - false is horizontal
14995     *
14996     * @return {boolean}
14997     *         - true if slider is vertical, and getting
14998     *         - false if the slider is horizontal, and getting
14999     */
15000    ;
15001
15002    _proto.vertical = function vertical(bool) {
15003      if (bool === undefined) {
15004        return this.vertical_ || false;
15005      }
15006
15007      this.vertical_ = !!bool;
15008
15009      if (this.vertical_) {
15010        this.addClass('vjs-slider-vertical');
15011      } else {
15012        this.addClass('vjs-slider-horizontal');
15013      }
15014    };
15015
15016    return Slider;
15017  }(Component);
15018
15019  Component.registerComponent('Slider', Slider);
15020
15021  var percentify = function percentify(time, end) {
15022    return clamp(time / end * 100, 0, 100).toFixed(2) + '%';
15023  };
15024  /**
15025   * Shows loading progress
15026   *
15027   * @extends Component
15028   */
15029
15030
15031  var LoadProgressBar = /*#__PURE__*/function (_Component) {
15032    inheritsLoose(LoadProgressBar, _Component);
15033
15034    /**
15035     * Creates an instance of this class.
15036     *
15037     * @param {Player} player
15038     *        The `Player` that this class should be attached to.
15039     *
15040     * @param {Object} [options]
15041     *        The key/value store of player options.
15042     */
15043    function LoadProgressBar(player, options) {
15044      var _this;
15045
15046      _this = _Component.call(this, player, options) || this;
15047      _this.partEls_ = [];
15048
15049      _this.on(player, 'progress', _this.update);
15050
15051      return _this;
15052    }
15053    /**
15054     * Create the `Component`'s DOM element
15055     *
15056     * @return {Element}
15057     *         The element that was created.
15058     */
15059
15060
15061    var _proto = LoadProgressBar.prototype;
15062
15063    _proto.createEl = function createEl$1() {
15064      var el = _Component.prototype.createEl.call(this, 'div', {
15065        className: 'vjs-load-progress'
15066      });
15067
15068      var wrapper = createEl('span', {
15069        className: 'vjs-control-text'
15070      });
15071      var loadedText = createEl('span', {
15072        textContent: this.localize('Loaded')
15073      });
15074      var separator = document.createTextNode(': ');
15075      this.percentageEl_ = createEl('span', {
15076        className: 'vjs-control-text-loaded-percentage',
15077        textContent: '0%'
15078      });
15079      el.appendChild(wrapper);
15080      wrapper.appendChild(loadedText);
15081      wrapper.appendChild(separator);
15082      wrapper.appendChild(this.percentageEl_);
15083      return el;
15084    };
15085
15086    _proto.dispose = function dispose() {
15087      this.partEls_ = null;
15088      this.percentageEl_ = null;
15089
15090      _Component.prototype.dispose.call(this);
15091    }
15092    /**
15093     * Update progress bar
15094     *
15095     * @param {EventTarget~Event} [event]
15096     *        The `progress` event that caused this function to run.
15097     *
15098     * @listens Player#progress
15099     */
15100    ;
15101
15102    _proto.update = function update(event) {
15103      var _this2 = this;
15104
15105      this.requestNamedAnimationFrame('LoadProgressBar#update', function () {
15106        var liveTracker = _this2.player_.liveTracker;
15107
15108        var buffered = _this2.player_.buffered();
15109
15110        var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration();
15111
15112        var bufferedEnd = _this2.player_.bufferedEnd();
15113
15114        var children = _this2.partEls_;
15115        var percent = percentify(bufferedEnd, duration);
15116
15117        if (_this2.percent_ !== percent) {
15118          // update the width of the progress bar
15119          _this2.el_.style.width = percent; // update the control-text
15120
15121          textContent(_this2.percentageEl_, percent);
15122          _this2.percent_ = percent;
15123        } // add child elements to represent the individual buffered time ranges
15124
15125
15126        for (var i = 0; i < buffered.length; i++) {
15127          var start = buffered.start(i);
15128          var end = buffered.end(i);
15129          var part = children[i];
15130
15131          if (!part) {
15132            part = _this2.el_.appendChild(createEl());
15133            children[i] = part;
15134          } //  only update if changed
15135
15136
15137          if (part.dataset.start === start && part.dataset.end === end) {
15138            continue;
15139          }
15140
15141          part.dataset.start = start;
15142          part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd)
15143
15144          part.style.left = percentify(start, bufferedEnd);
15145          part.style.width = percentify(end - start, bufferedEnd);
15146        } // remove unused buffered range elements
15147
15148
15149        for (var _i = children.length; _i > buffered.length; _i--) {
15150          _this2.el_.removeChild(children[_i - 1]);
15151        }
15152
15153        children.length = buffered.length;
15154      });
15155    };
15156
15157    return LoadProgressBar;
15158  }(Component);
15159
15160  Component.registerComponent('LoadProgressBar', LoadProgressBar);
15161
15162  /**
15163   * Time tooltips display a time above the progress bar.
15164   *
15165   * @extends Component
15166   */
15167
15168  var TimeTooltip = /*#__PURE__*/function (_Component) {
15169    inheritsLoose(TimeTooltip, _Component);
15170
15171    /**
15172     * Creates an instance of this class.
15173     *
15174     * @param {Player} player
15175     *        The {@link Player} that this class should be attached to.
15176     *
15177     * @param {Object} [options]
15178     *        The key/value store of player options.
15179     */
15180    function TimeTooltip(player, options) {
15181      var _this;
15182
15183      _this = _Component.call(this, player, options) || this;
15184      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15185      return _this;
15186    }
15187    /**
15188     * Create the time tooltip DOM element
15189     *
15190     * @return {Element}
15191     *         The element that was created.
15192     */
15193
15194
15195    var _proto = TimeTooltip.prototype;
15196
15197    _proto.createEl = function createEl() {
15198      return _Component.prototype.createEl.call(this, 'div', {
15199        className: 'vjs-time-tooltip'
15200      }, {
15201        'aria-hidden': 'true'
15202      });
15203    }
15204    /**
15205     * Updates the position of the time tooltip relative to the `SeekBar`.
15206     *
15207     * @param {Object} seekBarRect
15208     *        The `ClientRect` for the {@link SeekBar} element.
15209     *
15210     * @param {number} seekBarPoint
15211     *        A number from 0 to 1, representing a horizontal reference point
15212     *        from the left edge of the {@link SeekBar}
15213     */
15214    ;
15215
15216    _proto.update = function update(seekBarRect, seekBarPoint, content) {
15217      var tooltipRect = findPosition(this.el_);
15218      var playerRect = getBoundingClientRect(this.player_.el());
15219      var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
15220      // for example, if the player isn't in the DOM for testing
15221
15222      if (!playerRect || !tooltipRect) {
15223        return;
15224      } // This is the space left of the `seekBarPoint` available within the bounds
15225      // of the player. We calculate any gap between the left edge of the player
15226      // and the left edge of the `SeekBar` and add the number of pixels in the
15227      // `SeekBar` before hitting the `seekBarPoint`
15228
15229
15230      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
15231      // of the player. We calculate the number of pixels from the `seekBarPoint`
15232      // to the right edge of the `SeekBar` and add to that any gap between the
15233      // right edge of the `SeekBar` and the player.
15234
15235      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
15236      // further to the right to center it over the `seekBarPoint`.
15237
15238      var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
15239      // the results of the space calculations above.
15240
15241      if (spaceLeftOfPoint < pullTooltipBy) {
15242        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15243      } else if (spaceRightOfPoint < pullTooltipBy) {
15244        pullTooltipBy = spaceRightOfPoint;
15245      } // Due to the imprecision of decimal/ratio based calculations and varying
15246      // rounding behaviors, there are cases where the spacing adjustment is off
15247      // by a pixel or two. This adds insurance to these calculations.
15248
15249
15250      if (pullTooltipBy < 0) {
15251        pullTooltipBy = 0;
15252      } else if (pullTooltipBy > tooltipRect.width) {
15253        pullTooltipBy = tooltipRect.width;
15254      }
15255
15256      this.el_.style.right = "-" + pullTooltipBy + "px";
15257      this.write(content);
15258    }
15259    /**
15260     * Write the time to the tooltip DOM element.
15261     *
15262     * @param {string} content
15263     *        The formatted time for the tooltip.
15264     */
15265    ;
15266
15267    _proto.write = function write(content) {
15268      textContent(this.el_, content);
15269    }
15270    /**
15271     * Updates the position of the time tooltip relative to the `SeekBar`.
15272     *
15273     * @param {Object} seekBarRect
15274     *        The `ClientRect` for the {@link SeekBar} element.
15275     *
15276     * @param {number} seekBarPoint
15277     *        A number from 0 to 1, representing a horizontal reference point
15278     *        from the left edge of the {@link SeekBar}
15279     *
15280     * @param {number} time
15281     *        The time to update the tooltip to, not used during live playback
15282     *
15283     * @param {Function} cb
15284     *        A function that will be called during the request animation frame
15285     *        for tooltips that need to do additional animations from the default
15286     */
15287    ;
15288
15289    _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
15290      var _this2 = this;
15291
15292      this.requestNamedAnimationFrame('TimeTooltip#updateTime', function () {
15293        var content;
15294
15295        var duration = _this2.player_.duration();
15296
15297        if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) {
15298          var liveWindow = _this2.player_.liveTracker.liveWindow();
15299
15300          var secondsBehind = liveWindow - seekBarPoint * liveWindow;
15301          content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
15302        } else {
15303          content = formatTime(time, duration);
15304        }
15305
15306        _this2.update(seekBarRect, seekBarPoint, content);
15307
15308        if (cb) {
15309          cb();
15310        }
15311      });
15312    };
15313
15314    return TimeTooltip;
15315  }(Component);
15316
15317  Component.registerComponent('TimeTooltip', TimeTooltip);
15318
15319  /**
15320   * Used by {@link SeekBar} to display media playback progress as part of the
15321   * {@link ProgressControl}.
15322   *
15323   * @extends Component
15324   */
15325
15326  var PlayProgressBar = /*#__PURE__*/function (_Component) {
15327    inheritsLoose(PlayProgressBar, _Component);
15328
15329    /**
15330     * Creates an instance of this class.
15331     *
15332     * @param {Player} player
15333     *        The {@link Player} that this class should be attached to.
15334     *
15335     * @param {Object} [options]
15336     *        The key/value store of player options.
15337     */
15338    function PlayProgressBar(player, options) {
15339      var _this;
15340
15341      _this = _Component.call(this, player, options) || this;
15342      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15343      return _this;
15344    }
15345    /**
15346     * Create the the DOM element for this class.
15347     *
15348     * @return {Element}
15349     *         The element that was created.
15350     */
15351
15352
15353    var _proto = PlayProgressBar.prototype;
15354
15355    _proto.createEl = function createEl() {
15356      return _Component.prototype.createEl.call(this, 'div', {
15357        className: 'vjs-play-progress vjs-slider-bar'
15358      }, {
15359        'aria-hidden': 'true'
15360      });
15361    }
15362    /**
15363     * Enqueues updates to its own DOM as well as the DOM of its
15364     * {@link TimeTooltip} child.
15365     *
15366     * @param {Object} seekBarRect
15367     *        The `ClientRect` for the {@link SeekBar} element.
15368     *
15369     * @param {number} seekBarPoint
15370     *        A number from 0 to 1, representing a horizontal reference point
15371     *        from the left edge of the {@link SeekBar}
15372     */
15373    ;
15374
15375    _proto.update = function update(seekBarRect, seekBarPoint) {
15376      var timeTooltip = this.getChild('timeTooltip');
15377
15378      if (!timeTooltip) {
15379        return;
15380      }
15381
15382      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15383      timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
15384    };
15385
15386    return PlayProgressBar;
15387  }(Component);
15388  /**
15389   * Default options for {@link PlayProgressBar}.
15390   *
15391   * @type {Object}
15392   * @private
15393   */
15394
15395
15396  PlayProgressBar.prototype.options_ = {
15397    children: []
15398  }; // Time tooltips should not be added to a player on mobile devices
15399
15400  if (!IS_IOS && !IS_ANDROID) {
15401    PlayProgressBar.prototype.options_.children.push('timeTooltip');
15402  }
15403
15404  Component.registerComponent('PlayProgressBar', PlayProgressBar);
15405
15406  /**
15407   * The {@link MouseTimeDisplay} component tracks mouse movement over the
15408   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
15409   * indicating the time which is represented by a given point in the
15410   * {@link ProgressControl}.
15411   *
15412   * @extends Component
15413   */
15414
15415  var MouseTimeDisplay = /*#__PURE__*/function (_Component) {
15416    inheritsLoose(MouseTimeDisplay, _Component);
15417
15418    /**
15419     * Creates an instance of this class.
15420     *
15421     * @param {Player} player
15422     *        The {@link Player} that this class should be attached to.
15423     *
15424     * @param {Object} [options]
15425     *        The key/value store of player options.
15426     */
15427    function MouseTimeDisplay(player, options) {
15428      var _this;
15429
15430      _this = _Component.call(this, player, options) || this;
15431      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15432      return _this;
15433    }
15434    /**
15435     * Create the DOM element for this class.
15436     *
15437     * @return {Element}
15438     *         The element that was created.
15439     */
15440
15441
15442    var _proto = MouseTimeDisplay.prototype;
15443
15444    _proto.createEl = function createEl() {
15445      return _Component.prototype.createEl.call(this, 'div', {
15446        className: 'vjs-mouse-display'
15447      });
15448    }
15449    /**
15450     * Enqueues updates to its own DOM as well as the DOM of its
15451     * {@link TimeTooltip} child.
15452     *
15453     * @param {Object} seekBarRect
15454     *        The `ClientRect` for the {@link SeekBar} element.
15455     *
15456     * @param {number} seekBarPoint
15457     *        A number from 0 to 1, representing a horizontal reference point
15458     *        from the left edge of the {@link SeekBar}
15459     */
15460    ;
15461
15462    _proto.update = function update(seekBarRect, seekBarPoint) {
15463      var _this2 = this;
15464
15465      var time = seekBarPoint * this.player_.duration();
15466      this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
15467        _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
15468      });
15469    };
15470
15471    return MouseTimeDisplay;
15472  }(Component);
15473  /**
15474   * Default options for `MouseTimeDisplay`
15475   *
15476   * @type {Object}
15477   * @private
15478   */
15479
15480
15481  MouseTimeDisplay.prototype.options_ = {
15482    children: ['timeTooltip']
15483  };
15484  Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
15485
15486  var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
15487
15488  var PAGE_KEY_MULTIPLIER = 12;
15489  /**
15490   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
15491   * as its `bar`.
15492   *
15493   * @extends Slider
15494   */
15495
15496  var SeekBar = /*#__PURE__*/function (_Slider) {
15497    inheritsLoose(SeekBar, _Slider);
15498
15499    /**
15500     * Creates an instance of this class.
15501     *
15502     * @param {Player} player
15503     *        The `Player` that this class should be attached to.
15504     *
15505     * @param {Object} [options]
15506     *        The key/value store of player options.
15507     */
15508    function SeekBar(player, options) {
15509      var _this;
15510
15511      _this = _Slider.call(this, player, options) || this;
15512
15513      _this.setEventHandlers_();
15514
15515      return _this;
15516    }
15517    /**
15518     * Sets the event handlers
15519     *
15520     * @private
15521     */
15522
15523
15524    var _proto = SeekBar.prototype;
15525
15526    _proto.setEventHandlers_ = function setEventHandlers_() {
15527      this.update_ = bind(this, this.update);
15528      this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
15529      this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
15530
15531      if (this.player_.liveTracker) {
15532        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
15533      } // when playing, let's ensure we smoothly update the play progress bar
15534      // via an interval
15535
15536
15537      this.updateInterval = null;
15538      this.on(this.player_, ['playing'], this.enableInterval_);
15539      this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
15540      // also, this causes the CPU to spike and eventually crash the page on IE11.
15541
15542      if ('hidden' in document && 'visibilityState' in document) {
15543        this.on(document, 'visibilitychange', this.toggleVisibility_);
15544      }
15545    };
15546
15547    _proto.toggleVisibility_ = function toggleVisibility_(e) {
15548      if (document.hidden) {
15549        this.disableInterval_(e);
15550      } else {
15551        this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
15552
15553        this.update();
15554      }
15555    };
15556
15557    _proto.enableInterval_ = function enableInterval_() {
15558      if (this.updateInterval) {
15559        return;
15560      }
15561
15562      this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
15563    };
15564
15565    _proto.disableInterval_ = function disableInterval_(e) {
15566      if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
15567        return;
15568      }
15569
15570      if (!this.updateInterval) {
15571        return;
15572      }
15573
15574      this.clearInterval(this.updateInterval);
15575      this.updateInterval = null;
15576    }
15577    /**
15578     * Create the `Component`'s DOM element
15579     *
15580     * @return {Element}
15581     *         The element that was created.
15582     */
15583    ;
15584
15585    _proto.createEl = function createEl() {
15586      return _Slider.prototype.createEl.call(this, 'div', {
15587        className: 'vjs-progress-holder'
15588      }, {
15589        'aria-label': this.localize('Progress Bar')
15590      });
15591    }
15592    /**
15593     * This function updates the play progress bar and accessibility
15594     * attributes to whatever is passed in.
15595     *
15596     * @param {EventTarget~Event} [event]
15597     *        The `timeupdate` or `ended` event that caused this to run.
15598     *
15599     * @listens Player#timeupdate
15600     *
15601     * @return {number}
15602     *          The current percent at a number from 0-1
15603     */
15604    ;
15605
15606    _proto.update = function update(event) {
15607      var _this2 = this;
15608
15609      var percent = _Slider.prototype.update.call(this);
15610
15611      this.requestNamedAnimationFrame('SeekBar#update', function () {
15612        var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_();
15613        var liveTracker = _this2.player_.liveTracker;
15614
15615        var duration = _this2.player_.duration();
15616
15617        if (liveTracker && liveTracker.isLive()) {
15618          duration = _this2.player_.liveTracker.liveCurrentTime();
15619        }
15620
15621        if (_this2.percent_ !== percent) {
15622          // machine readable value of progress bar (percentage complete)
15623          _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
15624
15625          _this2.percent_ = percent;
15626        }
15627
15628        if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) {
15629          // human readable value of progress bar (time complete)
15630          _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
15631
15632          _this2.currentTime_ = currentTime;
15633          _this2.duration_ = duration;
15634        } // update the progress bar time tooltip with the current time
15635
15636
15637        if (_this2.bar) {
15638          _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress());
15639        }
15640      });
15641      return percent;
15642    }
15643    /**
15644     * Get the value of current time but allows for smooth scrubbing,
15645     * when player can't keep up.
15646     *
15647     * @return {number}
15648     *         The current time value to display
15649     *
15650     * @private
15651     */
15652    ;
15653
15654    _proto.getCurrentTime_ = function getCurrentTime_() {
15655      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15656    }
15657    /**
15658     * Get the percentage of media played so far.
15659     *
15660     * @return {number}
15661     *         The percentage of media played so far (0 to 1).
15662     */
15663    ;
15664
15665    _proto.getPercent = function getPercent() {
15666      var currentTime = this.getCurrentTime_();
15667      var percent;
15668      var liveTracker = this.player_.liveTracker;
15669
15670      if (liveTracker && liveTracker.isLive()) {
15671        percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
15672
15673        if (liveTracker.atLiveEdge()) {
15674          percent = 1;
15675        }
15676      } else {
15677        percent = currentTime / this.player_.duration();
15678      }
15679
15680      return percent;
15681    }
15682    /**
15683     * Handle mouse down on seek bar
15684     *
15685     * @param {EventTarget~Event} event
15686     *        The `mousedown` event that caused this to run.
15687     *
15688     * @listens mousedown
15689     */
15690    ;
15691
15692    _proto.handleMouseDown = function handleMouseDown(event) {
15693      if (!isSingleLeftClick(event)) {
15694        return;
15695      } // Stop event propagation to prevent double fire in progress-control.js
15696
15697
15698      event.stopPropagation();
15699      this.player_.scrubbing(true);
15700      this.videoWasPlaying = !this.player_.paused();
15701      this.player_.pause();
15702
15703      _Slider.prototype.handleMouseDown.call(this, event);
15704    }
15705    /**
15706     * Handle mouse move on seek bar
15707     *
15708     * @param {EventTarget~Event} event
15709     *        The `mousemove` event that caused this to run.
15710     *
15711     * @listens mousemove
15712     */
15713    ;
15714
15715    _proto.handleMouseMove = function handleMouseMove(event) {
15716      if (!isSingleLeftClick(event)) {
15717        return;
15718      }
15719
15720      var newTime;
15721      var distance = this.calculateDistance(event);
15722      var liveTracker = this.player_.liveTracker;
15723
15724      if (!liveTracker || !liveTracker.isLive()) {
15725        newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
15726
15727        if (newTime === this.player_.duration()) {
15728          newTime = newTime - 0.1;
15729        }
15730      } else {
15731        if (distance >= 0.99) {
15732          liveTracker.seekToLiveEdge();
15733          return;
15734        }
15735
15736        var seekableStart = liveTracker.seekableStart();
15737        var seekableEnd = liveTracker.liveCurrentTime();
15738        newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
15739
15740        if (newTime >= seekableEnd) {
15741          newTime = seekableEnd;
15742        } // Compensate for precision differences so that currentTime is not less
15743        // than seekable start
15744
15745
15746        if (newTime <= seekableStart) {
15747          newTime = seekableStart + 0.1;
15748        } // On android seekableEnd can be Infinity sometimes,
15749        // this will cause newTime to be Infinity, which is
15750        // not a valid currentTime.
15751
15752
15753        if (newTime === Infinity) {
15754          return;
15755        }
15756      } // Set new time (tell player to seek to new time)
15757
15758
15759      this.player_.currentTime(newTime);
15760    };
15761
15762    _proto.enable = function enable() {
15763      _Slider.prototype.enable.call(this);
15764
15765      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15766
15767      if (!mouseTimeDisplay) {
15768        return;
15769      }
15770
15771      mouseTimeDisplay.show();
15772    };
15773
15774    _proto.disable = function disable() {
15775      _Slider.prototype.disable.call(this);
15776
15777      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15778
15779      if (!mouseTimeDisplay) {
15780        return;
15781      }
15782
15783      mouseTimeDisplay.hide();
15784    }
15785    /**
15786     * Handle mouse up on seek bar
15787     *
15788     * @param {EventTarget~Event} event
15789     *        The `mouseup` event that caused this to run.
15790     *
15791     * @listens mouseup
15792     */
15793    ;
15794
15795    _proto.handleMouseUp = function handleMouseUp(event) {
15796      _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
15797
15798
15799      if (event) {
15800        event.stopPropagation();
15801      }
15802
15803      this.player_.scrubbing(false);
15804      /**
15805       * Trigger timeupdate because we're done seeking and the time has changed.
15806       * This is particularly useful for if the player is paused to time the time displays.
15807       *
15808       * @event Tech#timeupdate
15809       * @type {EventTarget~Event}
15810       */
15811
15812      this.player_.trigger({
15813        type: 'timeupdate',
15814        target: this,
15815        manuallyTriggered: true
15816      });
15817
15818      if (this.videoWasPlaying) {
15819        silencePromise(this.player_.play());
15820      } else {
15821        // We're done seeking and the time has changed.
15822        // If the player is paused, make sure we display the correct time on the seek bar.
15823        this.update_();
15824      }
15825    }
15826    /**
15827     * Move more quickly fast forward for keyboard-only users
15828     */
15829    ;
15830
15831    _proto.stepForward = function stepForward() {
15832      this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
15833    }
15834    /**
15835     * Move more quickly rewind for keyboard-only users
15836     */
15837    ;
15838
15839    _proto.stepBack = function stepBack() {
15840      this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
15841    }
15842    /**
15843     * Toggles the playback state of the player
15844     * This gets called when enter or space is used on the seekbar
15845     *
15846     * @param {EventTarget~Event} event
15847     *        The `keydown` event that caused this function to be called
15848     *
15849     */
15850    ;
15851
15852    _proto.handleAction = function handleAction(event) {
15853      if (this.player_.paused()) {
15854        this.player_.play();
15855      } else {
15856        this.player_.pause();
15857      }
15858    }
15859    /**
15860     * Called when this SeekBar has focus and a key gets pressed down.
15861     * Supports the following keys:
15862     *
15863     *   Space or Enter key fire a click event
15864     *   Home key moves to start of the timeline
15865     *   End key moves to end of the timeline
15866     *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
15867     *   PageDown key moves back a larger step than ArrowDown
15868     *   PageUp key moves forward a large step
15869     *
15870     * @param {EventTarget~Event} event
15871     *        The `keydown` event that caused this function to be called.
15872     *
15873     * @listens keydown
15874     */
15875    ;
15876
15877    _proto.handleKeyDown = function handleKeyDown(event) {
15878      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
15879        event.preventDefault();
15880        event.stopPropagation();
15881        this.handleAction(event);
15882      } else if (keycode.isEventKey(event, 'Home')) {
15883        event.preventDefault();
15884        event.stopPropagation();
15885        this.player_.currentTime(0);
15886      } else if (keycode.isEventKey(event, 'End')) {
15887        event.preventDefault();
15888        event.stopPropagation();
15889        this.player_.currentTime(this.player_.duration());
15890      } else if (/^[0-9]$/.test(keycode(event))) {
15891        event.preventDefault();
15892        event.stopPropagation();
15893        var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
15894        this.player_.currentTime(this.player_.duration() * gotoFraction);
15895      } else if (keycode.isEventKey(event, 'PgDn')) {
15896        event.preventDefault();
15897        event.stopPropagation();
15898        this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15899      } else if (keycode.isEventKey(event, 'PgUp')) {
15900        event.preventDefault();
15901        event.stopPropagation();
15902        this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15903      } else {
15904        // Pass keydown handling up for unsupported keys
15905        _Slider.prototype.handleKeyDown.call(this, event);
15906      }
15907    };
15908
15909    _proto.dispose = function dispose() {
15910      this.disableInterval_();
15911      this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
15912
15913      if (this.player_.liveTracker) {
15914        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
15915      }
15916
15917      this.off(this.player_, ['playing'], this.enableInterval_);
15918      this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
15919      // also, this causes the CPU to spike and eventually crash the page on IE11.
15920
15921      if ('hidden' in document && 'visibilityState' in document) {
15922        this.off(document, 'visibilitychange', this.toggleVisibility_);
15923      }
15924
15925      _Slider.prototype.dispose.call(this);
15926    };
15927
15928    return SeekBar;
15929  }(Slider);
15930  /**
15931   * Default options for the `SeekBar`
15932   *
15933   * @type {Object}
15934   * @private
15935   */
15936
15937
15938  SeekBar.prototype.options_ = {
15939    children: ['loadProgressBar', 'playProgressBar'],
15940    barName: 'playProgressBar'
15941  }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
15942
15943  if (!IS_IOS && !IS_ANDROID) {
15944    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
15945  }
15946
15947  Component.registerComponent('SeekBar', SeekBar);
15948
15949  /**
15950   * The Progress Control component contains the seek bar, load progress,
15951   * and play progress.
15952   *
15953   * @extends Component
15954   */
15955
15956  var ProgressControl = /*#__PURE__*/function (_Component) {
15957    inheritsLoose(ProgressControl, _Component);
15958
15959    /**
15960     * Creates an instance of this class.
15961     *
15962     * @param {Player} player
15963     *        The `Player` that this class should be attached to.
15964     *
15965     * @param {Object} [options]
15966     *        The key/value store of player options.
15967     */
15968    function ProgressControl(player, options) {
15969      var _this;
15970
15971      _this = _Component.call(this, player, options) || this;
15972      _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
15973      _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
15974
15975      _this.enable();
15976
15977      return _this;
15978    }
15979    /**
15980     * Create the `Component`'s DOM element
15981     *
15982     * @return {Element}
15983     *         The element that was created.
15984     */
15985
15986
15987    var _proto = ProgressControl.prototype;
15988
15989    _proto.createEl = function createEl() {
15990      return _Component.prototype.createEl.call(this, 'div', {
15991        className: 'vjs-progress-control vjs-control'
15992      });
15993    }
15994    /**
15995     * When the mouse moves over the `ProgressControl`, the pointer position
15996     * gets passed down to the `MouseTimeDisplay` component.
15997     *
15998     * @param {EventTarget~Event} event
15999     *        The `mousemove` event that caused this function to run.
16000     *
16001     * @listen mousemove
16002     */
16003    ;
16004
16005    _proto.handleMouseMove = function handleMouseMove(event) {
16006      var seekBar = this.getChild('seekBar');
16007
16008      if (!seekBar) {
16009        return;
16010      }
16011
16012      var playProgressBar = seekBar.getChild('playProgressBar');
16013      var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
16014
16015      if (!playProgressBar && !mouseTimeDisplay) {
16016        return;
16017      }
16018
16019      var seekBarEl = seekBar.el();
16020      var seekBarRect = findPosition(seekBarEl);
16021      var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
16022      // that it's possible to trigger this behavior outside the boundaries of
16023      // the `SeekBar`. This ensures we stay within it at all times.
16024
16025      seekBarPoint = clamp(seekBarPoint, 0, 1);
16026
16027      if (mouseTimeDisplay) {
16028        mouseTimeDisplay.update(seekBarRect, seekBarPoint);
16029      }
16030
16031      if (playProgressBar) {
16032        playProgressBar.update(seekBarRect, seekBar.getProgress());
16033      }
16034    }
16035    /**
16036     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
16037     *
16038     * @method ProgressControl#throttledHandleMouseSeek
16039     * @param {EventTarget~Event} event
16040     *        The `mousemove` event that caused this function to run.
16041     *
16042     * @listen mousemove
16043     * @listen touchmove
16044     */
16045
16046    /**
16047     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
16048     *
16049     * @param {EventTarget~Event} event
16050     *        `mousedown` or `touchstart` event that triggered this function
16051     *
16052     * @listens mousemove
16053     * @listens touchmove
16054     */
16055    ;
16056
16057    _proto.handleMouseSeek = function handleMouseSeek(event) {
16058      var seekBar = this.getChild('seekBar');
16059
16060      if (seekBar) {
16061        seekBar.handleMouseMove(event);
16062      }
16063    }
16064    /**
16065     * Are controls are currently enabled for this progress control.
16066     *
16067     * @return {boolean}
16068     *         true if controls are enabled, false otherwise
16069     */
16070    ;
16071
16072    _proto.enabled = function enabled() {
16073      return this.enabled_;
16074    }
16075    /**
16076     * Disable all controls on the progress control and its children
16077     */
16078    ;
16079
16080    _proto.disable = function disable() {
16081      this.children().forEach(function (child) {
16082        return child.disable && child.disable();
16083      });
16084
16085      if (!this.enabled()) {
16086        return;
16087      }
16088
16089      this.off(['mousedown', 'touchstart'], this.handleMouseDown);
16090      this.off(this.el_, 'mousemove', this.handleMouseMove);
16091      this.handleMouseUp();
16092      this.addClass('disabled');
16093      this.enabled_ = false;
16094    }
16095    /**
16096     * Enable all controls on the progress control and its children
16097     */
16098    ;
16099
16100    _proto.enable = function enable() {
16101      this.children().forEach(function (child) {
16102        return child.enable && child.enable();
16103      });
16104
16105      if (this.enabled()) {
16106        return;
16107      }
16108
16109      this.on(['mousedown', 'touchstart'], this.handleMouseDown);
16110      this.on(this.el_, 'mousemove', this.handleMouseMove);
16111      this.removeClass('disabled');
16112      this.enabled_ = true;
16113    }
16114    /**
16115     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
16116     *
16117     * @param {EventTarget~Event} event
16118     *        `mousedown` or `touchstart` event that triggered this function
16119     *
16120     * @listens mousedown
16121     * @listens touchstart
16122     */
16123    ;
16124
16125    _proto.handleMouseDown = function handleMouseDown(event) {
16126      var doc = this.el_.ownerDocument;
16127      var seekBar = this.getChild('seekBar');
16128
16129      if (seekBar) {
16130        seekBar.handleMouseDown(event);
16131      }
16132
16133      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
16134      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
16135      this.on(doc, 'mouseup', this.handleMouseUp);
16136      this.on(doc, 'touchend', this.handleMouseUp);
16137    }
16138    /**
16139     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
16140     *
16141     * @param {EventTarget~Event} event
16142     *        `mouseup` or `touchend` event that triggered this function.
16143     *
16144     * @listens touchend
16145     * @listens mouseup
16146     */
16147    ;
16148
16149    _proto.handleMouseUp = function handleMouseUp(event) {
16150      var doc = this.el_.ownerDocument;
16151      var seekBar = this.getChild('seekBar');
16152
16153      if (seekBar) {
16154        seekBar.handleMouseUp(event);
16155      }
16156
16157      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
16158      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
16159      this.off(doc, 'mouseup', this.handleMouseUp);
16160      this.off(doc, 'touchend', this.handleMouseUp);
16161    };
16162
16163    return ProgressControl;
16164  }(Component);
16165  /**
16166   * Default options for `ProgressControl`
16167   *
16168   * @type {Object}
16169   * @private
16170   */
16171
16172
16173  ProgressControl.prototype.options_ = {
16174    children: ['seekBar']
16175  };
16176  Component.registerComponent('ProgressControl', ProgressControl);
16177
16178  /**
16179   * Toggle Picture-in-Picture mode
16180   *
16181   * @extends Button
16182   */
16183
16184  var PictureInPictureToggle = /*#__PURE__*/function (_Button) {
16185    inheritsLoose(PictureInPictureToggle, _Button);
16186
16187    /**
16188     * Creates an instance of this class.
16189     *
16190     * @param {Player} player
16191     *        The `Player` that this class should be attached to.
16192     *
16193     * @param {Object} [options]
16194     *        The key/value store of player options.
16195     *
16196     * @listens Player#enterpictureinpicture
16197     * @listens Player#leavepictureinpicture
16198     */
16199    function PictureInPictureToggle(player, options) {
16200      var _this;
16201
16202      _this = _Button.call(this, player, options) || this;
16203
16204      _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange);
16205
16206      _this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], _this.handlePictureInPictureEnabledChange); // TODO: Deactivate button on player emptied event.
16207
16208
16209      _this.disable();
16210
16211      return _this;
16212    }
16213    /**
16214     * Builds the default DOM `className`.
16215     *
16216     * @return {string}
16217     *         The DOM `className` for this object.
16218     */
16219
16220
16221    var _proto = PictureInPictureToggle.prototype;
16222
16223    _proto.buildCSSClass = function buildCSSClass() {
16224      return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this);
16225    }
16226    /**
16227     * Enables or disables button based on document.pictureInPictureEnabled property value
16228     * or on value returned by player.disablePictureInPicture() method.
16229     */
16230    ;
16231
16232    _proto.handlePictureInPictureEnabledChange = function handlePictureInPictureEnabledChange() {
16233      if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) {
16234        this.enable();
16235      } else {
16236        this.disable();
16237      }
16238    }
16239    /**
16240     * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
16241     *
16242     * @param {EventTarget~Event} [event]
16243     *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
16244     *        called.
16245     *
16246     * @listens Player#enterpictureinpicture
16247     * @listens Player#leavepictureinpicture
16248     */
16249    ;
16250
16251    _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) {
16252      if (this.player_.isInPictureInPicture()) {
16253        this.controlText('Exit Picture-in-Picture');
16254      } else {
16255        this.controlText('Picture-in-Picture');
16256      }
16257
16258      this.handlePictureInPictureEnabledChange();
16259    }
16260    /**
16261     * This gets called when an `PictureInPictureToggle` is "clicked". See
16262     * {@link ClickableComponent} for more detailed information on what a click can be.
16263     *
16264     * @param {EventTarget~Event} [event]
16265     *        The `keydown`, `tap`, or `click` event that caused this function to be
16266     *        called.
16267     *
16268     * @listens tap
16269     * @listens click
16270     */
16271    ;
16272
16273    _proto.handleClick = function handleClick(event) {
16274      if (!this.player_.isInPictureInPicture()) {
16275        this.player_.requestPictureInPicture();
16276      } else {
16277        this.player_.exitPictureInPicture();
16278      }
16279    };
16280
16281    return PictureInPictureToggle;
16282  }(Button);
16283  /**
16284   * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
16285   *
16286   * @type {string}
16287   * @private
16288   */
16289
16290
16291  PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
16292  Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
16293
16294  /**
16295   * Toggle fullscreen video
16296   *
16297   * @extends Button
16298   */
16299
16300  var FullscreenToggle = /*#__PURE__*/function (_Button) {
16301    inheritsLoose(FullscreenToggle, _Button);
16302
16303    /**
16304     * Creates an instance of this class.
16305     *
16306     * @param {Player} player
16307     *        The `Player` that this class should be attached to.
16308     *
16309     * @param {Object} [options]
16310     *        The key/value store of player options.
16311     */
16312    function FullscreenToggle(player, options) {
16313      var _this;
16314
16315      _this = _Button.call(this, player, options) || this;
16316
16317      _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
16318
16319      if (document[player.fsApi_.fullscreenEnabled] === false) {
16320        _this.disable();
16321      }
16322
16323      return _this;
16324    }
16325    /**
16326     * Builds the default DOM `className`.
16327     *
16328     * @return {string}
16329     *         The DOM `className` for this object.
16330     */
16331
16332
16333    var _proto = FullscreenToggle.prototype;
16334
16335    _proto.buildCSSClass = function buildCSSClass() {
16336      return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
16337    }
16338    /**
16339     * Handles fullscreenchange on the player and change control text accordingly.
16340     *
16341     * @param {EventTarget~Event} [event]
16342     *        The {@link Player#fullscreenchange} event that caused this function to be
16343     *        called.
16344     *
16345     * @listens Player#fullscreenchange
16346     */
16347    ;
16348
16349    _proto.handleFullscreenChange = function handleFullscreenChange(event) {
16350      if (this.player_.isFullscreen()) {
16351        this.controlText('Non-Fullscreen');
16352      } else {
16353        this.controlText('Fullscreen');
16354      }
16355    }
16356    /**
16357     * This gets called when an `FullscreenToggle` is "clicked". See
16358     * {@link ClickableComponent} for more detailed information on what a click can be.
16359     *
16360     * @param {EventTarget~Event} [event]
16361     *        The `keydown`, `tap`, or `click` event that caused this function to be
16362     *        called.
16363     *
16364     * @listens tap
16365     * @listens click
16366     */
16367    ;
16368
16369    _proto.handleClick = function handleClick(event) {
16370      if (!this.player_.isFullscreen()) {
16371        this.player_.requestFullscreen();
16372      } else {
16373        this.player_.exitFullscreen();
16374      }
16375    };
16376
16377    return FullscreenToggle;
16378  }(Button);
16379  /**
16380   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
16381   *
16382   * @type {string}
16383   * @private
16384   */
16385
16386
16387  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
16388  Component.registerComponent('FullscreenToggle', FullscreenToggle);
16389
16390  /**
16391   * Check if volume control is supported and if it isn't hide the
16392   * `Component` that was passed  using the `vjs-hidden` class.
16393   *
16394   * @param {Component} self
16395   *        The component that should be hidden if volume is unsupported
16396   *
16397   * @param {Player} player
16398   *        A reference to the player
16399   *
16400   * @private
16401   */
16402  var checkVolumeSupport = function checkVolumeSupport(self, player) {
16403    // hide volume controls when they're not supported by the current tech
16404    if (player.tech_ && !player.tech_.featuresVolumeControl) {
16405      self.addClass('vjs-hidden');
16406    }
16407
16408    self.on(player, 'loadstart', function () {
16409      if (!player.tech_.featuresVolumeControl) {
16410        self.addClass('vjs-hidden');
16411      } else {
16412        self.removeClass('vjs-hidden');
16413      }
16414    });
16415  };
16416
16417  /**
16418   * Shows volume level
16419   *
16420   * @extends Component
16421   */
16422
16423  var VolumeLevel = /*#__PURE__*/function (_Component) {
16424    inheritsLoose(VolumeLevel, _Component);
16425
16426    function VolumeLevel() {
16427      return _Component.apply(this, arguments) || this;
16428    }
16429
16430    var _proto = VolumeLevel.prototype;
16431
16432    /**
16433     * Create the `Component`'s DOM element
16434     *
16435     * @return {Element}
16436     *         The element that was created.
16437     */
16438    _proto.createEl = function createEl() {
16439      return _Component.prototype.createEl.call(this, 'div', {
16440        className: 'vjs-volume-level',
16441        innerHTML: '<span class="vjs-control-text"></span>'
16442      });
16443    };
16444
16445    return VolumeLevel;
16446  }(Component);
16447
16448  Component.registerComponent('VolumeLevel', VolumeLevel);
16449
16450  /**
16451   * The bar that contains the volume level and can be clicked on to adjust the level
16452   *
16453   * @extends Slider
16454   */
16455
16456  var VolumeBar = /*#__PURE__*/function (_Slider) {
16457    inheritsLoose(VolumeBar, _Slider);
16458
16459    /**
16460     * Creates an instance of this class.
16461     *
16462     * @param {Player} player
16463     *        The `Player` that this class should be attached to.
16464     *
16465     * @param {Object} [options]
16466     *        The key/value store of player options.
16467     */
16468    function VolumeBar(player, options) {
16469      var _this;
16470
16471      _this = _Slider.call(this, player, options) || this;
16472
16473      _this.on('slideractive', _this.updateLastVolume_);
16474
16475      _this.on(player, 'volumechange', _this.updateARIAAttributes);
16476
16477      player.ready(function () {
16478        return _this.updateARIAAttributes();
16479      });
16480      return _this;
16481    }
16482    /**
16483     * Create the `Component`'s DOM element
16484     *
16485     * @return {Element}
16486     *         The element that was created.
16487     */
16488
16489
16490    var _proto = VolumeBar.prototype;
16491
16492    _proto.createEl = function createEl() {
16493      return _Slider.prototype.createEl.call(this, 'div', {
16494        className: 'vjs-volume-bar vjs-slider-bar'
16495      }, {
16496        'aria-label': this.localize('Volume Level'),
16497        'aria-live': 'polite'
16498      });
16499    }
16500    /**
16501     * Handle mouse down on volume bar
16502     *
16503     * @param {EventTarget~Event} event
16504     *        The `mousedown` event that caused this to run.
16505     *
16506     * @listens mousedown
16507     */
16508    ;
16509
16510    _proto.handleMouseDown = function handleMouseDown(event) {
16511      if (!isSingleLeftClick(event)) {
16512        return;
16513      }
16514
16515      _Slider.prototype.handleMouseDown.call(this, event);
16516    }
16517    /**
16518     * Handle movement events on the {@link VolumeMenuButton}.
16519     *
16520     * @param {EventTarget~Event} event
16521     *        The event that caused this function to run.
16522     *
16523     * @listens mousemove
16524     */
16525    ;
16526
16527    _proto.handleMouseMove = function handleMouseMove(event) {
16528      if (!isSingleLeftClick(event)) {
16529        return;
16530      }
16531
16532      this.checkMuted();
16533      this.player_.volume(this.calculateDistance(event));
16534    }
16535    /**
16536     * If the player is muted unmute it.
16537     */
16538    ;
16539
16540    _proto.checkMuted = function checkMuted() {
16541      if (this.player_.muted()) {
16542        this.player_.muted(false);
16543      }
16544    }
16545    /**
16546     * Get percent of volume level
16547     *
16548     * @return {number}
16549     *         Volume level percent as a decimal number.
16550     */
16551    ;
16552
16553    _proto.getPercent = function getPercent() {
16554      if (this.player_.muted()) {
16555        return 0;
16556      }
16557
16558      return this.player_.volume();
16559    }
16560    /**
16561     * Increase volume level for keyboard users
16562     */
16563    ;
16564
16565    _proto.stepForward = function stepForward() {
16566      this.checkMuted();
16567      this.player_.volume(this.player_.volume() + 0.1);
16568    }
16569    /**
16570     * Decrease volume level for keyboard users
16571     */
16572    ;
16573
16574    _proto.stepBack = function stepBack() {
16575      this.checkMuted();
16576      this.player_.volume(this.player_.volume() - 0.1);
16577    }
16578    /**
16579     * Update ARIA accessibility attributes
16580     *
16581     * @param {EventTarget~Event} [event]
16582     *        The `volumechange` event that caused this function to run.
16583     *
16584     * @listens Player#volumechange
16585     */
16586    ;
16587
16588    _proto.updateARIAAttributes = function updateARIAAttributes(event) {
16589      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
16590      this.el_.setAttribute('aria-valuenow', ariaValue);
16591      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
16592    }
16593    /**
16594     * Returns the current value of the player volume as a percentage
16595     *
16596     * @private
16597     */
16598    ;
16599
16600    _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
16601      return Math.round(this.player_.volume() * 100);
16602    }
16603    /**
16604     * When user starts dragging the VolumeBar, store the volume and listen for
16605     * the end of the drag. When the drag ends, if the volume was set to zero,
16606     * set lastVolume to the stored volume.
16607     *
16608     * @listens slideractive
16609     * @private
16610     */
16611    ;
16612
16613    _proto.updateLastVolume_ = function updateLastVolume_() {
16614      var _this2 = this;
16615
16616      var volumeBeforeDrag = this.player_.volume();
16617      this.one('sliderinactive', function () {
16618        if (_this2.player_.volume() === 0) {
16619          _this2.player_.lastVolume_(volumeBeforeDrag);
16620        }
16621      });
16622    };
16623
16624    return VolumeBar;
16625  }(Slider);
16626  /**
16627   * Default options for the `VolumeBar`
16628   *
16629   * @type {Object}
16630   * @private
16631   */
16632
16633
16634  VolumeBar.prototype.options_ = {
16635    children: ['volumeLevel'],
16636    barName: 'volumeLevel'
16637  };
16638  /**
16639   * Call the update event for this Slider when this event happens on the player.
16640   *
16641   * @type {string}
16642   */
16643
16644  VolumeBar.prototype.playerEvent = 'volumechange';
16645  Component.registerComponent('VolumeBar', VolumeBar);
16646
16647  /**
16648   * The component for controlling the volume level
16649   *
16650   * @extends Component
16651   */
16652
16653  var VolumeControl = /*#__PURE__*/function (_Component) {
16654    inheritsLoose(VolumeControl, _Component);
16655
16656    /**
16657     * Creates an instance of this class.
16658     *
16659     * @param {Player} player
16660     *        The `Player` that this class should be attached to.
16661     *
16662     * @param {Object} [options={}]
16663     *        The key/value store of player options.
16664     */
16665    function VolumeControl(player, options) {
16666      var _this;
16667
16668      if (options === void 0) {
16669        options = {};
16670      }
16671
16672      options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
16673      // the VolumeBar is turned on.
16674
16675      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
16676        options.volumeBar = options.volumeBar || {};
16677        options.volumeBar.vertical = options.vertical;
16678      }
16679
16680      _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
16681
16682      checkVolumeSupport(assertThisInitialized(_this), player);
16683      _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
16684
16685      _this.on('mousedown', _this.handleMouseDown);
16686
16687      _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
16688      // is dragging) or in focus we do not want to hide the VolumeBar
16689
16690
16691      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
16692        _this.volumeBar.addClass('vjs-slider-active');
16693
16694        _this.addClass('vjs-slider-active');
16695
16696        _this.trigger('slideractive');
16697      });
16698
16699      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
16700        _this.volumeBar.removeClass('vjs-slider-active');
16701
16702        _this.removeClass('vjs-slider-active');
16703
16704        _this.trigger('sliderinactive');
16705      });
16706
16707      return _this;
16708    }
16709    /**
16710     * Create the `Component`'s DOM element
16711     *
16712     * @return {Element}
16713     *         The element that was created.
16714     */
16715
16716
16717    var _proto = VolumeControl.prototype;
16718
16719    _proto.createEl = function createEl() {
16720      var orientationClass = 'vjs-volume-horizontal';
16721
16722      if (this.options_.vertical) {
16723        orientationClass = 'vjs-volume-vertical';
16724      }
16725
16726      return _Component.prototype.createEl.call(this, 'div', {
16727        className: "vjs-volume-control vjs-control " + orientationClass
16728      });
16729    }
16730    /**
16731     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16732     *
16733     * @param {EventTarget~Event} event
16734     *        `mousedown` or `touchstart` event that triggered this function
16735     *
16736     * @listens mousedown
16737     * @listens touchstart
16738     */
16739    ;
16740
16741    _proto.handleMouseDown = function handleMouseDown(event) {
16742      var doc = this.el_.ownerDocument;
16743      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
16744      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
16745      this.on(doc, 'mouseup', this.handleMouseUp);
16746      this.on(doc, 'touchend', this.handleMouseUp);
16747    }
16748    /**
16749     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
16750     *
16751     * @param {EventTarget~Event} event
16752     *        `mouseup` or `touchend` event that triggered this function.
16753     *
16754     * @listens touchend
16755     * @listens mouseup
16756     */
16757    ;
16758
16759    _proto.handleMouseUp = function handleMouseUp(event) {
16760      var doc = this.el_.ownerDocument;
16761      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
16762      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
16763      this.off(doc, 'mouseup', this.handleMouseUp);
16764      this.off(doc, 'touchend', this.handleMouseUp);
16765    }
16766    /**
16767     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16768     *
16769     * @param {EventTarget~Event} event
16770     *        `mousedown` or `touchstart` event that triggered this function
16771     *
16772     * @listens mousedown
16773     * @listens touchstart
16774     */
16775    ;
16776
16777    _proto.handleMouseMove = function handleMouseMove(event) {
16778      this.volumeBar.handleMouseMove(event);
16779    };
16780
16781    return VolumeControl;
16782  }(Component);
16783  /**
16784   * Default options for the `VolumeControl`
16785   *
16786   * @type {Object}
16787   * @private
16788   */
16789
16790
16791  VolumeControl.prototype.options_ = {
16792    children: ['volumeBar']
16793  };
16794  Component.registerComponent('VolumeControl', VolumeControl);
16795
16796  /**
16797   * Check if muting volume is supported and if it isn't hide the mute toggle
16798   * button.
16799   *
16800   * @param {Component} self
16801   *        A reference to the mute toggle button
16802   *
16803   * @param {Player} player
16804   *        A reference to the player
16805   *
16806   * @private
16807   */
16808  var checkMuteSupport = function checkMuteSupport(self, player) {
16809    // hide mute toggle button if it's not supported by the current tech
16810    if (player.tech_ && !player.tech_.featuresMuteControl) {
16811      self.addClass('vjs-hidden');
16812    }
16813
16814    self.on(player, 'loadstart', function () {
16815      if (!player.tech_.featuresMuteControl) {
16816        self.addClass('vjs-hidden');
16817      } else {
16818        self.removeClass('vjs-hidden');
16819      }
16820    });
16821  };
16822
16823  /**
16824   * A button component for muting the audio.
16825   *
16826   * @extends Button
16827   */
16828
16829  var MuteToggle = /*#__PURE__*/function (_Button) {
16830    inheritsLoose(MuteToggle, _Button);
16831
16832    /**
16833     * Creates an instance of this class.
16834     *
16835     * @param {Player} player
16836     *        The `Player` that this class should be attached to.
16837     *
16838     * @param {Object} [options]
16839     *        The key/value store of player options.
16840     */
16841    function MuteToggle(player, options) {
16842      var _this;
16843
16844      _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
16845
16846      checkMuteSupport(assertThisInitialized(_this), player);
16847
16848      _this.on(player, ['loadstart', 'volumechange'], _this.update);
16849
16850      return _this;
16851    }
16852    /**
16853     * Builds the default DOM `className`.
16854     *
16855     * @return {string}
16856     *         The DOM `className` for this object.
16857     */
16858
16859
16860    var _proto = MuteToggle.prototype;
16861
16862    _proto.buildCSSClass = function buildCSSClass() {
16863      return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
16864    }
16865    /**
16866     * This gets called when an `MuteToggle` is "clicked". See
16867     * {@link ClickableComponent} for more detailed information on what a click can be.
16868     *
16869     * @param {EventTarget~Event} [event]
16870     *        The `keydown`, `tap`, or `click` event that caused this function to be
16871     *        called.
16872     *
16873     * @listens tap
16874     * @listens click
16875     */
16876    ;
16877
16878    _proto.handleClick = function handleClick(event) {
16879      var vol = this.player_.volume();
16880      var lastVolume = this.player_.lastVolume_();
16881
16882      if (vol === 0) {
16883        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
16884        this.player_.volume(volumeToSet);
16885        this.player_.muted(false);
16886      } else {
16887        this.player_.muted(this.player_.muted() ? false : true);
16888      }
16889    }
16890    /**
16891     * Update the `MuteToggle` button based on the state of `volume` and `muted`
16892     * on the player.
16893     *
16894     * @param {EventTarget~Event} [event]
16895     *        The {@link Player#loadstart} event if this function was called
16896     *        through an event.
16897     *
16898     * @listens Player#loadstart
16899     * @listens Player#volumechange
16900     */
16901    ;
16902
16903    _proto.update = function update(event) {
16904      this.updateIcon_();
16905      this.updateControlText_();
16906    }
16907    /**
16908     * Update the appearance of the `MuteToggle` icon.
16909     *
16910     * Possible states (given `level` variable below):
16911     * - 0: crossed out
16912     * - 1: zero bars of volume
16913     * - 2: one bar of volume
16914     * - 3: two bars of volume
16915     *
16916     * @private
16917     */
16918    ;
16919
16920    _proto.updateIcon_ = function updateIcon_() {
16921      var vol = this.player_.volume();
16922      var level = 3; // in iOS when a player is loaded with muted attribute
16923      // and volume is changed with a native mute button
16924      // we want to make sure muted state is updated
16925
16926      if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
16927        this.player_.muted(this.player_.tech_.el_.muted);
16928      }
16929
16930      if (vol === 0 || this.player_.muted()) {
16931        level = 0;
16932      } else if (vol < 0.33) {
16933        level = 1;
16934      } else if (vol < 0.67) {
16935        level = 2;
16936      } // TODO improve muted icon classes
16937
16938
16939      for (var i = 0; i < 4; i++) {
16940        removeClass(this.el_, "vjs-vol-" + i);
16941      }
16942
16943      addClass(this.el_, "vjs-vol-" + level);
16944    }
16945    /**
16946     * If `muted` has changed on the player, update the control text
16947     * (`title` attribute on `vjs-mute-control` element and content of
16948     * `vjs-control-text` element).
16949     *
16950     * @private
16951     */
16952    ;
16953
16954    _proto.updateControlText_ = function updateControlText_() {
16955      var soundOff = this.player_.muted() || this.player_.volume() === 0;
16956      var text = soundOff ? 'Unmute' : 'Mute';
16957
16958      if (this.controlText() !== text) {
16959        this.controlText(text);
16960      }
16961    };
16962
16963    return MuteToggle;
16964  }(Button);
16965  /**
16966   * The text that should display over the `MuteToggle`s controls. Added for localization.
16967   *
16968   * @type {string}
16969   * @private
16970   */
16971
16972
16973  MuteToggle.prototype.controlText_ = 'Mute';
16974  Component.registerComponent('MuteToggle', MuteToggle);
16975
16976  /**
16977   * A Component to contain the MuteToggle and VolumeControl so that
16978   * they can work together.
16979   *
16980   * @extends Component
16981   */
16982
16983  var VolumePanel = /*#__PURE__*/function (_Component) {
16984    inheritsLoose(VolumePanel, _Component);
16985
16986    /**
16987     * Creates an instance of this class.
16988     *
16989     * @param {Player} player
16990     *        The `Player` that this class should be attached to.
16991     *
16992     * @param {Object} [options={}]
16993     *        The key/value store of player options.
16994     */
16995    function VolumePanel(player, options) {
16996      var _this;
16997
16998      if (options === void 0) {
16999        options = {};
17000      }
17001
17002      if (typeof options.inline !== 'undefined') {
17003        options.inline = options.inline;
17004      } else {
17005        options.inline = true;
17006      } // pass the inline option down to the VolumeControl as vertical if
17007      // the VolumeControl is on.
17008
17009
17010      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
17011        options.volumeControl = options.volumeControl || {};
17012        options.volumeControl.vertical = !options.inline;
17013      }
17014
17015      _this = _Component.call(this, player, options) || this;
17016
17017      _this.on(player, ['loadstart'], _this.volumePanelState_);
17018
17019      _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress);
17020
17021      _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp);
17022
17023      _this.on('keydown', _this.handleKeyPress);
17024
17025      _this.on('mouseover', _this.handleMouseOver);
17026
17027      _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and
17028      // is dragging) we do not want to hide the VolumeBar
17029
17030
17031      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
17032
17033      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
17034
17035      return _this;
17036    }
17037    /**
17038     * Add vjs-slider-active class to the VolumePanel
17039     *
17040     * @listens VolumeControl#slideractive
17041     * @private
17042     */
17043
17044
17045    var _proto = VolumePanel.prototype;
17046
17047    _proto.sliderActive_ = function sliderActive_() {
17048      this.addClass('vjs-slider-active');
17049    }
17050    /**
17051     * Removes vjs-slider-active class to the VolumePanel
17052     *
17053     * @listens VolumeControl#sliderinactive
17054     * @private
17055     */
17056    ;
17057
17058    _proto.sliderInactive_ = function sliderInactive_() {
17059      this.removeClass('vjs-slider-active');
17060    }
17061    /**
17062     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
17063     * depending on MuteToggle and VolumeControl state
17064     *
17065     * @listens Player#loadstart
17066     * @private
17067     */
17068    ;
17069
17070    _proto.volumePanelState_ = function volumePanelState_() {
17071      // hide volume panel if neither volume control or mute toggle
17072      // are displayed
17073      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
17074        this.addClass('vjs-hidden');
17075      } // if only mute toggle is visible we don't want
17076      // volume panel expanding when hovered or active
17077
17078
17079      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
17080        this.addClass('vjs-mute-toggle-only');
17081      }
17082    }
17083    /**
17084     * Create the `Component`'s DOM element
17085     *
17086     * @return {Element}
17087     *         The element that was created.
17088     */
17089    ;
17090
17091    _proto.createEl = function createEl() {
17092      var orientationClass = 'vjs-volume-panel-horizontal';
17093
17094      if (!this.options_.inline) {
17095        orientationClass = 'vjs-volume-panel-vertical';
17096      }
17097
17098      return _Component.prototype.createEl.call(this, 'div', {
17099        className: "vjs-volume-panel vjs-control " + orientationClass
17100      });
17101    }
17102    /**
17103     * Dispose of the `volume-panel` and all child components.
17104     */
17105    ;
17106
17107    _proto.dispose = function dispose() {
17108      this.handleMouseOut();
17109
17110      _Component.prototype.dispose.call(this);
17111    }
17112    /**
17113     * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
17114     * the volume panel and sets focus on `MuteToggle`.
17115     *
17116     * @param {EventTarget~Event} event
17117     *        The `keyup` event that caused this function to be called.
17118     *
17119     * @listens keyup
17120     */
17121    ;
17122
17123    _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) {
17124      if (keycode.isEventKey(event, 'Esc')) {
17125        this.muteToggle.focus();
17126      }
17127    }
17128    /**
17129     * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
17130     * Turns on listening for `mouseover` event. When they happen it
17131     * calls `this.handleMouseOver`.
17132     *
17133     * @param {EventTarget~Event} event
17134     *        The `mouseover` event that caused this function to be called.
17135     *
17136     * @listens mouseover
17137     */
17138    ;
17139
17140    _proto.handleMouseOver = function handleMouseOver(event) {
17141      this.addClass('vjs-hover');
17142      on(document, 'keyup', bind(this, this.handleKeyPress));
17143    }
17144    /**
17145     * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
17146     * Turns on listening for `mouseout` event. When they happen it
17147     * calls `this.handleMouseOut`.
17148     *
17149     * @param {EventTarget~Event} event
17150     *        The `mouseout` event that caused this function to be called.
17151     *
17152     * @listens mouseout
17153     */
17154    ;
17155
17156    _proto.handleMouseOut = function handleMouseOut(event) {
17157      this.removeClass('vjs-hover');
17158      off(document, 'keyup', bind(this, this.handleKeyPress));
17159    }
17160    /**
17161     * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
17162     * looking for ESC, which hides the `VolumeControl`.
17163     *
17164     * @param {EventTarget~Event} event
17165     *        The keypress that triggered this event.
17166     *
17167     * @listens keydown | keyup
17168     */
17169    ;
17170
17171    _proto.handleKeyPress = function handleKeyPress(event) {
17172      if (keycode.isEventKey(event, 'Esc')) {
17173        this.handleMouseOut();
17174      }
17175    };
17176
17177    return VolumePanel;
17178  }(Component);
17179  /**
17180   * Default options for the `VolumeControl`
17181   *
17182   * @type {Object}
17183   * @private
17184   */
17185
17186
17187  VolumePanel.prototype.options_ = {
17188    children: ['muteToggle', 'volumeControl']
17189  };
17190  Component.registerComponent('VolumePanel', VolumePanel);
17191
17192  /**
17193   * The Menu component is used to build popup menus, including subtitle and
17194   * captions selection menus.
17195   *
17196   * @extends Component
17197   */
17198
17199  var Menu = /*#__PURE__*/function (_Component) {
17200    inheritsLoose(Menu, _Component);
17201
17202    /**
17203     * Create an instance of this class.
17204     *
17205     * @param {Player} player
17206     *        the player that this component should attach to
17207     *
17208     * @param {Object} [options]
17209     *        Object of option names and values
17210     *
17211     */
17212    function Menu(player, options) {
17213      var _this;
17214
17215      _this = _Component.call(this, player, options) || this;
17216
17217      if (options) {
17218        _this.menuButton_ = options.menuButton;
17219      }
17220
17221      _this.focusedChild_ = -1;
17222
17223      _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container.
17224
17225
17226      _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur);
17227      _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick);
17228      return _this;
17229    }
17230    /**
17231     * Add event listeners to the {@link MenuItem}.
17232     *
17233     * @param {Object} component
17234     *        The instance of the `MenuItem` to add listeners to.
17235     *
17236     */
17237
17238
17239    var _proto = Menu.prototype;
17240
17241    _proto.addEventListenerForItem = function addEventListenerForItem(component) {
17242      if (!(component instanceof Component)) {
17243        return;
17244      }
17245
17246      this.on(component, 'blur', this.boundHandleBlur_);
17247      this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
17248    }
17249    /**
17250     * Remove event listeners from the {@link MenuItem}.
17251     *
17252     * @param {Object} component
17253     *        The instance of the `MenuItem` to remove listeners.
17254     *
17255     */
17256    ;
17257
17258    _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
17259      if (!(component instanceof Component)) {
17260        return;
17261      }
17262
17263      this.off(component, 'blur', this.boundHandleBlur_);
17264      this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
17265    }
17266    /**
17267     * This method will be called indirectly when the component has been added
17268     * before the component adds to the new menu instance by `addItem`.
17269     * In this case, the original menu instance will remove the component
17270     * by calling `removeChild`.
17271     *
17272     * @param {Object} component
17273     *        The instance of the `MenuItem`
17274     */
17275    ;
17276
17277    _proto.removeChild = function removeChild(component) {
17278      if (typeof component === 'string') {
17279        component = this.getChild(component);
17280      }
17281
17282      this.removeEventListenerForItem(component);
17283
17284      _Component.prototype.removeChild.call(this, component);
17285    }
17286    /**
17287     * Add a {@link MenuItem} to the menu.
17288     *
17289     * @param {Object|string} component
17290     *        The name or instance of the `MenuItem` to add.
17291     *
17292     */
17293    ;
17294
17295    _proto.addItem = function addItem(component) {
17296      var childComponent = this.addChild(component);
17297
17298      if (childComponent) {
17299        this.addEventListenerForItem(childComponent);
17300      }
17301    }
17302    /**
17303     * Create the `Menu`s DOM element.
17304     *
17305     * @return {Element}
17306     *         the element that was created
17307     */
17308    ;
17309
17310    _proto.createEl = function createEl$1() {
17311      var contentElType = this.options_.contentElType || 'ul';
17312      this.contentEl_ = createEl(contentElType, {
17313        className: 'vjs-menu-content'
17314      });
17315      this.contentEl_.setAttribute('role', 'menu');
17316
17317      var el = _Component.prototype.createEl.call(this, 'div', {
17318        append: this.contentEl_,
17319        className: 'vjs-menu'
17320      });
17321
17322      el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
17323      // where a click on the parent is significant
17324
17325      on(el, 'click', function (event) {
17326        event.preventDefault();
17327        event.stopImmediatePropagation();
17328      });
17329      return el;
17330    };
17331
17332    _proto.dispose = function dispose() {
17333      this.contentEl_ = null;
17334      this.boundHandleBlur_ = null;
17335      this.boundHandleTapClick_ = null;
17336
17337      _Component.prototype.dispose.call(this);
17338    }
17339    /**
17340     * Called when a `MenuItem` loses focus.
17341     *
17342     * @param {EventTarget~Event} event
17343     *        The `blur` event that caused this function to be called.
17344     *
17345     * @listens blur
17346     */
17347    ;
17348
17349    _proto.handleBlur = function handleBlur(event) {
17350      var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
17351
17352      if (!this.children().some(function (element) {
17353        return element.el() === relatedTarget;
17354      })) {
17355        var btn = this.menuButton_;
17356
17357        if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
17358          btn.unpressButton();
17359        }
17360      }
17361    }
17362    /**
17363     * Called when a `MenuItem` gets clicked or tapped.
17364     *
17365     * @param {EventTarget~Event} event
17366     *        The `click` or `tap` event that caused this function to be called.
17367     *
17368     * @listens click,tap
17369     */
17370    ;
17371
17372    _proto.handleTapClick = function handleTapClick(event) {
17373      // Unpress the associated MenuButton, and move focus back to it
17374      if (this.menuButton_) {
17375        this.menuButton_.unpressButton();
17376        var childComponents = this.children();
17377
17378        if (!Array.isArray(childComponents)) {
17379          return;
17380        }
17381
17382        var foundComponent = childComponents.filter(function (component) {
17383          return component.el() === event.target;
17384        })[0];
17385
17386        if (!foundComponent) {
17387          return;
17388        } // don't focus menu button if item is a caption settings item
17389        // because focus will move elsewhere
17390
17391
17392        if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
17393          this.menuButton_.focus();
17394        }
17395      }
17396    }
17397    /**
17398     * Handle a `keydown` event on this menu. This listener is added in the constructor.
17399     *
17400     * @param {EventTarget~Event} event
17401     *        A `keydown` event that happened on the menu.
17402     *
17403     * @listens keydown
17404     */
17405    ;
17406
17407    _proto.handleKeyDown = function handleKeyDown(event) {
17408      // Left and Down Arrows
17409      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
17410        event.preventDefault();
17411        event.stopPropagation();
17412        this.stepForward(); // Up and Right Arrows
17413      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
17414        event.preventDefault();
17415        event.stopPropagation();
17416        this.stepBack();
17417      }
17418    }
17419    /**
17420     * Move to next (lower) menu item for keyboard users.
17421     */
17422    ;
17423
17424    _proto.stepForward = function stepForward() {
17425      var stepChild = 0;
17426
17427      if (this.focusedChild_ !== undefined) {
17428        stepChild = this.focusedChild_ + 1;
17429      }
17430
17431      this.focus(stepChild);
17432    }
17433    /**
17434     * Move to previous (higher) menu item for keyboard users.
17435     */
17436    ;
17437
17438    _proto.stepBack = function stepBack() {
17439      var stepChild = 0;
17440
17441      if (this.focusedChild_ !== undefined) {
17442        stepChild = this.focusedChild_ - 1;
17443      }
17444
17445      this.focus(stepChild);
17446    }
17447    /**
17448     * Set focus on a {@link MenuItem} in the `Menu`.
17449     *
17450     * @param {Object|string} [item=0]
17451     *        Index of child item set focus on.
17452     */
17453    ;
17454
17455    _proto.focus = function focus(item) {
17456      if (item === void 0) {
17457        item = 0;
17458      }
17459
17460      var children = this.children().slice();
17461      var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
17462
17463      if (haveTitle) {
17464        children.shift();
17465      }
17466
17467      if (children.length > 0) {
17468        if (item < 0) {
17469          item = 0;
17470        } else if (item >= children.length) {
17471          item = children.length - 1;
17472        }
17473
17474        this.focusedChild_ = item;
17475        children[item].el_.focus();
17476      }
17477    };
17478
17479    return Menu;
17480  }(Component);
17481
17482  Component.registerComponent('Menu', Menu);
17483
17484  /**
17485   * A `MenuButton` class for any popup {@link Menu}.
17486   *
17487   * @extends Component
17488   */
17489
17490  var MenuButton = /*#__PURE__*/function (_Component) {
17491    inheritsLoose(MenuButton, _Component);
17492
17493    /**
17494     * Creates an instance of this class.
17495     *
17496     * @param {Player} player
17497     *        The `Player` that this class should be attached to.
17498     *
17499     * @param {Object} [options={}]
17500     *        The key/value store of player options.
17501     */
17502    function MenuButton(player, options) {
17503      var _this;
17504
17505      if (options === void 0) {
17506        options = {};
17507      }
17508
17509      _this = _Component.call(this, player, options) || this;
17510      _this.menuButton_ = new Button(player, options);
17511
17512      _this.menuButton_.controlText(_this.controlText_);
17513
17514      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
17515
17516
17517      var buttonClass = Button.prototype.buildCSSClass();
17518      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
17519
17520      _this.menuButton_.removeClass('vjs-control');
17521
17522      _this.addChild(_this.menuButton_);
17523
17524      _this.update();
17525
17526      _this.enabled_ = true;
17527
17528      _this.on(_this.menuButton_, 'tap', _this.handleClick);
17529
17530      _this.on(_this.menuButton_, 'click', _this.handleClick);
17531
17532      _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown);
17533
17534      _this.on(_this.menuButton_, 'mouseenter', function () {
17535        _this.addClass('vjs-hover');
17536
17537        _this.menu.show();
17538
17539        on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp));
17540      });
17541
17542      _this.on('mouseleave', _this.handleMouseLeave);
17543
17544      _this.on('keydown', _this.handleSubmenuKeyDown);
17545
17546      return _this;
17547    }
17548    /**
17549     * Update the menu based on the current state of its items.
17550     */
17551
17552
17553    var _proto = MenuButton.prototype;
17554
17555    _proto.update = function update() {
17556      var menu = this.createMenu();
17557
17558      if (this.menu) {
17559        this.menu.dispose();
17560        this.removeChild(this.menu);
17561      }
17562
17563      this.menu = menu;
17564      this.addChild(menu);
17565      /**
17566       * Track the state of the menu button
17567       *
17568       * @type {Boolean}
17569       * @private
17570       */
17571
17572      this.buttonPressed_ = false;
17573      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17574
17575      if (this.items && this.items.length <= this.hideThreshold_) {
17576        this.hide();
17577      } else {
17578        this.show();
17579      }
17580    }
17581    /**
17582     * Create the menu and add all items to it.
17583     *
17584     * @return {Menu}
17585     *         The constructed menu
17586     */
17587    ;
17588
17589    _proto.createMenu = function createMenu() {
17590      var menu = new Menu(this.player_, {
17591        menuButton: this
17592      });
17593      /**
17594       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
17595       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
17596       * it here because every time we run `createMenu` we need to reset the value.
17597       *
17598       * @protected
17599       * @type {Number}
17600       */
17601
17602      this.hideThreshold_ = 0; // Add a title list item to the top
17603
17604      if (this.options_.title) {
17605        var titleEl = createEl('li', {
17606          className: 'vjs-menu-title',
17607          innerHTML: toTitleCase(this.options_.title),
17608          tabIndex: -1
17609        });
17610        this.hideThreshold_ += 1;
17611        var titleComponent = new Component(this.player_, {
17612          el: titleEl
17613        });
17614        menu.addItem(titleComponent);
17615      }
17616
17617      this.items = this.createItems();
17618
17619      if (this.items) {
17620        // Add menu items to the menu
17621        for (var i = 0; i < this.items.length; i++) {
17622          menu.addItem(this.items[i]);
17623        }
17624      }
17625
17626      return menu;
17627    }
17628    /**
17629     * Create the list of menu items. Specific to each subclass.
17630     *
17631     * @abstract
17632     */
17633    ;
17634
17635    _proto.createItems = function createItems() {}
17636    /**
17637     * Create the `MenuButtons`s DOM element.
17638     *
17639     * @return {Element}
17640     *         The element that gets created.
17641     */
17642    ;
17643
17644    _proto.createEl = function createEl() {
17645      return _Component.prototype.createEl.call(this, 'div', {
17646        className: this.buildWrapperCSSClass()
17647      }, {});
17648    }
17649    /**
17650     * Allow sub components to stack CSS class names for the wrapper element
17651     *
17652     * @return {string}
17653     *         The constructed wrapper DOM `className`
17654     */
17655    ;
17656
17657    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
17658      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17659
17660      if (this.options_.inline === true) {
17661        menuButtonClass += '-inline';
17662      } else {
17663        menuButtonClass += '-popup';
17664      } // TODO: Fix the CSS so that this isn't necessary
17665
17666
17667      var buttonClass = Button.prototype.buildCSSClass();
17668      return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
17669    }
17670    /**
17671     * Builds the default DOM `className`.
17672     *
17673     * @return {string}
17674     *         The DOM `className` for this object.
17675     */
17676    ;
17677
17678    _proto.buildCSSClass = function buildCSSClass() {
17679      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17680
17681      if (this.options_.inline === true) {
17682        menuButtonClass += '-inline';
17683      } else {
17684        menuButtonClass += '-popup';
17685      }
17686
17687      return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
17688    }
17689    /**
17690     * Get or set the localized control text that will be used for accessibility.
17691     *
17692     * > NOTE: This will come from the internal `menuButton_` element.
17693     *
17694     * @param {string} [text]
17695     *        Control text for element.
17696     *
17697     * @param {Element} [el=this.menuButton_.el()]
17698     *        Element to set the title on.
17699     *
17700     * @return {string}
17701     *         - The control text when getting
17702     */
17703    ;
17704
17705    _proto.controlText = function controlText(text, el) {
17706      if (el === void 0) {
17707        el = this.menuButton_.el();
17708      }
17709
17710      return this.menuButton_.controlText(text, el);
17711    }
17712    /**
17713     * Dispose of the `menu-button` and all child components.
17714     */
17715    ;
17716
17717    _proto.dispose = function dispose() {
17718      this.handleMouseLeave();
17719
17720      _Component.prototype.dispose.call(this);
17721    }
17722    /**
17723     * Handle a click on a `MenuButton`.
17724     * See {@link ClickableComponent#handleClick} for instances where this is called.
17725     *
17726     * @param {EventTarget~Event} event
17727     *        The `keydown`, `tap`, or `click` event that caused this function to be
17728     *        called.
17729     *
17730     * @listens tap
17731     * @listens click
17732     */
17733    ;
17734
17735    _proto.handleClick = function handleClick(event) {
17736      if (this.buttonPressed_) {
17737        this.unpressButton();
17738      } else {
17739        this.pressButton();
17740      }
17741    }
17742    /**
17743     * Handle `mouseleave` for `MenuButton`.
17744     *
17745     * @param {EventTarget~Event} event
17746     *        The `mouseleave` event that caused this function to be called.
17747     *
17748     * @listens mouseleave
17749     */
17750    ;
17751
17752    _proto.handleMouseLeave = function handleMouseLeave(event) {
17753      this.removeClass('vjs-hover');
17754      off(document, 'keyup', bind(this, this.handleMenuKeyUp));
17755    }
17756    /**
17757     * Set the focus to the actual button, not to this element
17758     */
17759    ;
17760
17761    _proto.focus = function focus() {
17762      this.menuButton_.focus();
17763    }
17764    /**
17765     * Remove the focus from the actual button, not this element
17766     */
17767    ;
17768
17769    _proto.blur = function blur() {
17770      this.menuButton_.blur();
17771    }
17772    /**
17773     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
17774     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17775     *
17776     * @param {EventTarget~Event} event
17777     *        The `keydown` event that caused this function to be called.
17778     *
17779     * @listens keydown
17780     */
17781    ;
17782
17783    _proto.handleKeyDown = function handleKeyDown(event) {
17784      // Escape or Tab unpress the 'button'
17785      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17786        if (this.buttonPressed_) {
17787          this.unpressButton();
17788        } // Don't preventDefault for Tab key - we still want to lose focus
17789
17790
17791        if (!keycode.isEventKey(event, 'Tab')) {
17792          event.preventDefault(); // Set focus back to the menu button's button
17793
17794          this.menuButton_.focus();
17795        } // Up Arrow or Down Arrow also 'press' the button to open the menu
17796
17797      } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
17798        if (!this.buttonPressed_) {
17799          event.preventDefault();
17800          this.pressButton();
17801        }
17802      }
17803    }
17804    /**
17805     * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
17806     * the constructor.
17807     *
17808     * @param {EventTarget~Event} event
17809     *        Key press event
17810     *
17811     * @listens keyup
17812     */
17813    ;
17814
17815    _proto.handleMenuKeyUp = function handleMenuKeyUp(event) {
17816      // Escape hides popup menu
17817      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17818        this.removeClass('vjs-hover');
17819      }
17820    }
17821    /**
17822     * This method name now delegates to `handleSubmenuKeyDown`. This means
17823     * anyone calling `handleSubmenuKeyPress` will not see their method calls
17824     * stop working.
17825     *
17826     * @param {EventTarget~Event} event
17827     *        The event that caused this function to be called.
17828     */
17829    ;
17830
17831    _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
17832      this.handleSubmenuKeyDown(event);
17833    }
17834    /**
17835     * Handle a `keydown` event on a sub-menu. The listener for this is added in
17836     * the constructor.
17837     *
17838     * @param {EventTarget~Event} event
17839     *        Key press event
17840     *
17841     * @listens keydown
17842     */
17843    ;
17844
17845    _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
17846      // Escape or Tab unpress the 'button'
17847      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17848        if (this.buttonPressed_) {
17849          this.unpressButton();
17850        } // Don't preventDefault for Tab key - we still want to lose focus
17851
17852
17853        if (!keycode.isEventKey(event, 'Tab')) {
17854          event.preventDefault(); // Set focus back to the menu button's button
17855
17856          this.menuButton_.focus();
17857        }
17858      }
17859    }
17860    /**
17861     * Put the current `MenuButton` into a pressed state.
17862     */
17863    ;
17864
17865    _proto.pressButton = function pressButton() {
17866      if (this.enabled_) {
17867        this.buttonPressed_ = true;
17868        this.menu.show();
17869        this.menu.lockShowing();
17870        this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
17871        // undesired scrolling behavior when the player is in an iframe
17872
17873        if (IS_IOS && isInFrame()) {
17874          // Return early so that the menu isn't focused
17875          return;
17876        }
17877
17878        this.menu.focus();
17879      }
17880    }
17881    /**
17882     * Take the current `MenuButton` out of a pressed state.
17883     */
17884    ;
17885
17886    _proto.unpressButton = function unpressButton() {
17887      if (this.enabled_) {
17888        this.buttonPressed_ = false;
17889        this.menu.unlockShowing();
17890        this.menu.hide();
17891        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17892      }
17893    }
17894    /**
17895     * Disable the `MenuButton`. Don't allow it to be clicked.
17896     */
17897    ;
17898
17899    _proto.disable = function disable() {
17900      this.unpressButton();
17901      this.enabled_ = false;
17902      this.addClass('vjs-disabled');
17903      this.menuButton_.disable();
17904    }
17905    /**
17906     * Enable the `MenuButton`. Allow it to be clicked.
17907     */
17908    ;
17909
17910    _proto.enable = function enable() {
17911      this.enabled_ = true;
17912      this.removeClass('vjs-disabled');
17913      this.menuButton_.enable();
17914    };
17915
17916    return MenuButton;
17917  }(Component);
17918
17919  Component.registerComponent('MenuButton', MenuButton);
17920
17921  /**
17922   * The base class for buttons that toggle specific  track types (e.g. subtitles).
17923   *
17924   * @extends MenuButton
17925   */
17926
17927  var TrackButton = /*#__PURE__*/function (_MenuButton) {
17928    inheritsLoose(TrackButton, _MenuButton);
17929
17930    /**
17931     * Creates an instance of this class.
17932     *
17933     * @param {Player} player
17934     *        The `Player` that this class should be attached to.
17935     *
17936     * @param {Object} [options]
17937     *        The key/value store of player options.
17938     */
17939    function TrackButton(player, options) {
17940      var _this;
17941
17942      var tracks = options.tracks;
17943      _this = _MenuButton.call(this, player, options) || this;
17944
17945      if (_this.items.length <= 1) {
17946        _this.hide();
17947      }
17948
17949      if (!tracks) {
17950        return assertThisInitialized(_this);
17951      }
17952
17953      var updateHandler = bind(assertThisInitialized(_this), _this.update);
17954      tracks.addEventListener('removetrack', updateHandler);
17955      tracks.addEventListener('addtrack', updateHandler);
17956
17957      _this.player_.on('ready', updateHandler);
17958
17959      _this.player_.on('dispose', function () {
17960        tracks.removeEventListener('removetrack', updateHandler);
17961        tracks.removeEventListener('addtrack', updateHandler);
17962      });
17963
17964      return _this;
17965    }
17966
17967    return TrackButton;
17968  }(MenuButton);
17969
17970  Component.registerComponent('TrackButton', TrackButton);
17971
17972  /**
17973   * @file menu-keys.js
17974   */
17975
17976  /**
17977    * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
17978    * Note that 'Enter' and 'Space' are not included here (otherwise they would
17979    * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
17980    * @typedef MenuKeys
17981    * @array
17982    */
17983  var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
17984
17985  /**
17986   * The component for a menu item. `<li>`
17987   *
17988   * @extends ClickableComponent
17989   */
17990
17991  var MenuItem = /*#__PURE__*/function (_ClickableComponent) {
17992    inheritsLoose(MenuItem, _ClickableComponent);
17993
17994    /**
17995     * Creates an instance of the this class.
17996     *
17997     * @param {Player} player
17998     *        The `Player` that this class should be attached to.
17999     *
18000     * @param {Object} [options={}]
18001     *        The key/value store of player options.
18002     *
18003     */
18004    function MenuItem(player, options) {
18005      var _this;
18006
18007      _this = _ClickableComponent.call(this, player, options) || this;
18008      _this.selectable = options.selectable;
18009      _this.isSelected_ = options.selected || false;
18010      _this.multiSelectable = options.multiSelectable;
18011
18012      _this.selected(_this.isSelected_);
18013
18014      if (_this.selectable) {
18015        if (_this.multiSelectable) {
18016          _this.el_.setAttribute('role', 'menuitemcheckbox');
18017        } else {
18018          _this.el_.setAttribute('role', 'menuitemradio');
18019        }
18020      } else {
18021        _this.el_.setAttribute('role', 'menuitem');
18022      }
18023
18024      return _this;
18025    }
18026    /**
18027     * Create the `MenuItem's DOM element
18028     *
18029     * @param {string} [type=li]
18030     *        Element's node type, not actually used, always set to `li`.
18031     *
18032     * @param {Object} [props={}]
18033     *        An object of properties that should be set on the element
18034     *
18035     * @param {Object} [attrs={}]
18036     *        An object of attributes that should be set on the element
18037     *
18038     * @return {Element}
18039     *         The element that gets created.
18040     */
18041
18042
18043    var _proto = MenuItem.prototype;
18044
18045    _proto.createEl = function createEl(type, props, attrs) {
18046      // The control is textual, not just an icon
18047      this.nonIconControl = true;
18048      return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
18049        className: 'vjs-menu-item',
18050        innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
18051        tabIndex: -1
18052      }, props), attrs);
18053    }
18054    /**
18055     * Ignore keys which are used by the menu, but pass any other ones up. See
18056     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
18057     *
18058     * @param {EventTarget~Event} event
18059     *        The `keydown` event that caused this function to be called.
18060     *
18061     * @listens keydown
18062     */
18063    ;
18064
18065    _proto.handleKeyDown = function handleKeyDown(event) {
18066      if (!MenuKeys.some(function (key) {
18067        return keycode.isEventKey(event, key);
18068      })) {
18069        // Pass keydown handling up for unused keys
18070        _ClickableComponent.prototype.handleKeyDown.call(this, event);
18071      }
18072    }
18073    /**
18074     * Any click on a `MenuItem` puts it into the selected state.
18075     * See {@link ClickableComponent#handleClick} for instances where this is called.
18076     *
18077     * @param {EventTarget~Event} event
18078     *        The `keydown`, `tap`, or `click` event that caused this function to be
18079     *        called.
18080     *
18081     * @listens tap
18082     * @listens click
18083     */
18084    ;
18085
18086    _proto.handleClick = function handleClick(event) {
18087      this.selected(true);
18088    }
18089    /**
18090     * Set the state for this menu item as selected or not.
18091     *
18092     * @param {boolean} selected
18093     *        if the menu item is selected or not
18094     */
18095    ;
18096
18097    _proto.selected = function selected(_selected) {
18098      if (this.selectable) {
18099        if (_selected) {
18100          this.addClass('vjs-selected');
18101          this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
18102          // so indicate selected state to screen reader in the control text.
18103
18104          this.controlText(', selected');
18105          this.isSelected_ = true;
18106        } else {
18107          this.removeClass('vjs-selected');
18108          this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
18109
18110          this.controlText('');
18111          this.isSelected_ = false;
18112        }
18113      }
18114    };
18115
18116    return MenuItem;
18117  }(ClickableComponent);
18118
18119  Component.registerComponent('MenuItem', MenuItem);
18120
18121  /**
18122   * The specific menu item type for selecting a language within a text track kind
18123   *
18124   * @extends MenuItem
18125   */
18126
18127  var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
18128    inheritsLoose(TextTrackMenuItem, _MenuItem);
18129
18130    /**
18131     * Creates an instance of this class.
18132     *
18133     * @param {Player} player
18134     *        The `Player` that this class should be attached to.
18135     *
18136     * @param {Object} [options]
18137     *        The key/value store of player options.
18138     */
18139    function TextTrackMenuItem(player, options) {
18140      var _this;
18141
18142      var track = options.track;
18143      var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
18144
18145      options.label = track.label || track.language || 'Unknown';
18146      options.selected = track.mode === 'showing';
18147      _this = _MenuItem.call(this, player, options) || this;
18148      _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
18149      // out empty kinds.
18150
18151      _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
18152
18153      var changeHandler = function changeHandler() {
18154        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
18155          args[_key] = arguments[_key];
18156        }
18157
18158        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
18159      };
18160
18161      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
18162        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
18163          args[_key2] = arguments[_key2];
18164        }
18165
18166        _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args);
18167      };
18168
18169      player.on(['loadstart', 'texttrackchange'], changeHandler);
18170      tracks.addEventListener('change', changeHandler);
18171      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
18172
18173      _this.on('dispose', function () {
18174        player.off(['loadstart', 'texttrackchange'], changeHandler);
18175        tracks.removeEventListener('change', changeHandler);
18176        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
18177      }); // iOS7 doesn't dispatch change events to TextTrackLists when an
18178      // associated track's mode changes. Without something like
18179      // Object.observe() (also not present on iOS7), it's not
18180      // possible to detect changes to the mode attribute and polyfill
18181      // the change event. As a poor substitute, we manually dispatch
18182      // change events whenever the controls modify the mode.
18183
18184
18185      if (tracks.onchange === undefined) {
18186        var event;
18187
18188        _this.on(['tap', 'click'], function () {
18189          if (typeof window$1.Event !== 'object') {
18190            // Android 2.3 throws an Illegal Constructor error for window.Event
18191            try {
18192              event = new window$1.Event('change');
18193            } catch (err) {// continue regardless of error
18194            }
18195          }
18196
18197          if (!event) {
18198            event = document.createEvent('Event');
18199            event.initEvent('change', true, true);
18200          }
18201
18202          tracks.dispatchEvent(event);
18203        });
18204      } // set the default state based on current tracks
18205
18206
18207      _this.handleTracksChange();
18208
18209      return _this;
18210    }
18211    /**
18212     * This gets called when an `TextTrackMenuItem` is "clicked". See
18213     * {@link ClickableComponent} for more detailed information on what a click can be.
18214     *
18215     * @param {EventTarget~Event} event
18216     *        The `keydown`, `tap`, or `click` event that caused this function to be
18217     *        called.
18218     *
18219     * @listens tap
18220     * @listens click
18221     */
18222
18223
18224    var _proto = TextTrackMenuItem.prototype;
18225
18226    _proto.handleClick = function handleClick(event) {
18227      var referenceTrack = this.track;
18228      var tracks = this.player_.textTracks();
18229
18230      _MenuItem.prototype.handleClick.call(this, event);
18231
18232      if (!tracks) {
18233        return;
18234      }
18235
18236      for (var i = 0; i < tracks.length; i++) {
18237        var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
18238        // skip it. We do not want to affect tracks of incompatible kind(s).
18239
18240        if (this.kinds.indexOf(track.kind) === -1) {
18241          continue;
18242        } // If this text track is the component's track and it is not showing,
18243        // set it to showing.
18244
18245
18246        if (track === referenceTrack) {
18247          if (track.mode !== 'showing') {
18248            track.mode = 'showing';
18249          } // If this text track is not the component's track and it is not
18250          // disabled, set it to disabled.
18251
18252        } else if (track.mode !== 'disabled') {
18253          track.mode = 'disabled';
18254        }
18255      }
18256    }
18257    /**
18258     * Handle text track list change
18259     *
18260     * @param {EventTarget~Event} event
18261     *        The `change` event that caused this function to be called.
18262     *
18263     * @listens TextTrackList#change
18264     */
18265    ;
18266
18267    _proto.handleTracksChange = function handleTracksChange(event) {
18268      var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
18269      // screen readers to read the appended control text unnecessarily
18270
18271      if (shouldBeSelected !== this.isSelected_) {
18272        this.selected(shouldBeSelected);
18273      }
18274    };
18275
18276    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
18277      if (this.track.mode === 'showing') {
18278        var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
18279
18280        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
18281          return;
18282        }
18283
18284        this.player_.cache_.selectedLanguage = {
18285          enabled: true,
18286          language: this.track.language,
18287          kind: this.track.kind
18288        };
18289      }
18290    };
18291
18292    _proto.dispose = function dispose() {
18293      // remove reference to track object on dispose
18294      this.track = null;
18295
18296      _MenuItem.prototype.dispose.call(this);
18297    };
18298
18299    return TextTrackMenuItem;
18300  }(MenuItem);
18301
18302  Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
18303
18304  /**
18305   * A special menu item for turning of a specific type of text track
18306   *
18307   * @extends TextTrackMenuItem
18308   */
18309
18310  var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
18311    inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
18312
18313    /**
18314     * Creates an instance of this class.
18315     *
18316     * @param {Player} player
18317     *        The `Player` that this class should be attached to.
18318     *
18319     * @param {Object} [options]
18320     *        The key/value store of player options.
18321     */
18322    function OffTextTrackMenuItem(player, options) {
18323      // Create pseudo track info
18324      // Requires options['kind']
18325      options.track = {
18326        player: player,
18327        // it is no longer necessary to store `kind` or `kinds` on the track itself
18328        // since they are now stored in the `kinds` property of all instances of
18329        // TextTrackMenuItem, but this will remain for backwards compatibility
18330        kind: options.kind,
18331        kinds: options.kinds,
18332        "default": false,
18333        mode: 'disabled'
18334      };
18335
18336      if (!options.kinds) {
18337        options.kinds = [options.kind];
18338      }
18339
18340      if (options.label) {
18341        options.track.label = options.label;
18342      } else {
18343        options.track.label = options.kinds.join(' and ') + ' off';
18344      } // MenuItem is selectable
18345
18346
18347      options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18348
18349      options.multiSelectable = false;
18350      return _TextTrackMenuItem.call(this, player, options) || this;
18351    }
18352    /**
18353     * Handle text track change
18354     *
18355     * @param {EventTarget~Event} event
18356     *        The event that caused this function to run
18357     */
18358
18359
18360    var _proto = OffTextTrackMenuItem.prototype;
18361
18362    _proto.handleTracksChange = function handleTracksChange(event) {
18363      var tracks = this.player().textTracks();
18364      var shouldBeSelected = true;
18365
18366      for (var i = 0, l = tracks.length; i < l; i++) {
18367        var track = tracks[i];
18368
18369        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
18370          shouldBeSelected = false;
18371          break;
18372        }
18373      } // Prevent redundant selected() calls because they may cause
18374      // screen readers to read the appended control text unnecessarily
18375
18376
18377      if (shouldBeSelected !== this.isSelected_) {
18378        this.selected(shouldBeSelected);
18379      }
18380    };
18381
18382    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
18383      var tracks = this.player().textTracks();
18384      var allHidden = true;
18385
18386      for (var i = 0, l = tracks.length; i < l; i++) {
18387        var track = tracks[i];
18388
18389        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
18390          allHidden = false;
18391          break;
18392        }
18393      }
18394
18395      if (allHidden) {
18396        this.player_.cache_.selectedLanguage = {
18397          enabled: false
18398        };
18399      }
18400    };
18401
18402    return OffTextTrackMenuItem;
18403  }(TextTrackMenuItem);
18404
18405  Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
18406
18407  /**
18408   * The base class for buttons that toggle specific text track types (e.g. subtitles)
18409   *
18410   * @extends MenuButton
18411   */
18412
18413  var TextTrackButton = /*#__PURE__*/function (_TrackButton) {
18414    inheritsLoose(TextTrackButton, _TrackButton);
18415
18416    /**
18417     * Creates an instance of this class.
18418     *
18419     * @param {Player} player
18420     *        The `Player` that this class should be attached to.
18421     *
18422     * @param {Object} [options={}]
18423     *        The key/value store of player options.
18424     */
18425    function TextTrackButton(player, options) {
18426      if (options === void 0) {
18427        options = {};
18428      }
18429
18430      options.tracks = player.textTracks();
18431      return _TrackButton.call(this, player, options) || this;
18432    }
18433    /**
18434     * Create a menu item for each text track
18435     *
18436     * @param {TextTrackMenuItem[]} [items=[]]
18437     *        Existing array of items to use during creation
18438     *
18439     * @return {TextTrackMenuItem[]}
18440     *         Array of menu items that were created
18441     */
18442
18443
18444    var _proto = TextTrackButton.prototype;
18445
18446    _proto.createItems = function createItems(items, TrackMenuItem) {
18447      if (items === void 0) {
18448        items = [];
18449      }
18450
18451      if (TrackMenuItem === void 0) {
18452        TrackMenuItem = TextTrackMenuItem;
18453      }
18454
18455      // Label is an override for the [track] off label
18456      // USed to localise captions/subtitles
18457      var label;
18458
18459      if (this.label_) {
18460        label = this.label_ + " off";
18461      } // Add an OFF menu item to turn all tracks off
18462
18463
18464      items.push(new OffTextTrackMenuItem(this.player_, {
18465        kinds: this.kinds_,
18466        kind: this.kind_,
18467        label: label
18468      }));
18469      this.hideThreshold_ += 1;
18470      var tracks = this.player_.textTracks();
18471
18472      if (!Array.isArray(this.kinds_)) {
18473        this.kinds_ = [this.kind_];
18474      }
18475
18476      for (var i = 0; i < tracks.length; i++) {
18477        var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
18478
18479        if (this.kinds_.indexOf(track.kind) > -1) {
18480          var item = new TrackMenuItem(this.player_, {
18481            track: track,
18482            kinds: this.kinds_,
18483            kind: this.kind_,
18484            // MenuItem is selectable
18485            selectable: true,
18486            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18487            multiSelectable: false
18488          });
18489          item.addClass("vjs-" + track.kind + "-menu-item");
18490          items.push(item);
18491        }
18492      }
18493
18494      return items;
18495    };
18496
18497    return TextTrackButton;
18498  }(TrackButton);
18499
18500  Component.registerComponent('TextTrackButton', TextTrackButton);
18501
18502  /**
18503   * The chapter track menu item
18504   *
18505   * @extends MenuItem
18506   */
18507
18508  var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
vendor: 5,535 bytes, lines 18509-18690
18509    inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
18510
18511    /**
18512     * Creates an instance of this class.
18513     *
18514     * @param {Player} player
18515     *        The `Player` that this class should be attached to.
18516     *
18517     * @param {Object} [options]
18518     *        The key/value store of player options.
18519     */
18520    function ChaptersTrackMenuItem(player, options) {
18521      var _this;
18522
18523      var track = options.track;
18524      var cue = options.cue;
18525      var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
18526
18527      options.selectable = true;
18528      options.multiSelectable = false;
18529      options.label = cue.text;
18530      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
18531      _this = _MenuItem.call(this, player, options) || this;
18532      _this.track = track;
18533      _this.cue = cue;
18534      track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update));
18535      return _this;
18536    }
18537    /**
18538     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
18539     * {@link ClickableComponent} for more detailed information on what a click can be.
18540     *
18541     * @param {EventTarget~Event} [event]
18542     *        The `keydown`, `tap`, or `click` event that caused this function to be
18543     *        called.
18544     *
18545     * @listens tap
18546     * @listens click
18547     */
18548
18549
18550    var _proto = ChaptersTrackMenuItem.prototype;
18551
18552    _proto.handleClick = function handleClick(event) {
18553      _MenuItem.prototype.handleClick.call(this);
18554
18555      this.player_.currentTime(this.cue.startTime);
18556      this.update(this.cue.startTime);
18557    }
18558    /**
18559     * Update chapter menu item
18560     *
18561     * @param {EventTarget~Event} [event]
18562     *        The `cuechange` event that caused this function to run.
18563     *
18564     * @listens TextTrack#cuechange
18565     */
18566    ;
18567
18568    _proto.update = function update(event) {
18569      var cue = this.cue;
18570      var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
18571
18572      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
18573    };
18574
18575    return ChaptersTrackMenuItem;
18576  }(MenuItem);
18577
18578  Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
18579
18580  /**
18581   * The button component for toggling and selecting chapters
18582   * Chapters act much differently than other text tracks
18583   * Cues are navigation vs. other tracks of alternative languages
18584   *
18585   * @extends TextTrackButton
18586   */
18587
18588  var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) {
18589    inheritsLoose(ChaptersButton, _TextTrackButton);
18590
18591    /**
18592     * Creates an instance of this class.
18593     *
18594     * @param {Player} player
18595     *        The `Player` that this class should be attached to.
18596     *
18597     * @param {Object} [options]
18598     *        The key/value store of player options.
18599     *
18600     * @param {Component~ReadyCallback} [ready]
18601     *        The function to call when this function is ready.
18602     */
18603    function ChaptersButton(player, options, ready) {
18604      return _TextTrackButton.call(this, player, options, ready) || this;
18605    }
18606    /**
18607     * Builds the default DOM `className`.
18608     *
18609     * @return {string}
18610     *         The DOM `className` for this object.
18611     */
18612
18613
18614    var _proto = ChaptersButton.prototype;
18615
18616    _proto.buildCSSClass = function buildCSSClass() {
18617      return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18618    };
18619
18620    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18621      return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18622    }
18623    /**
18624     * Update the menu based on the current state of its items.
18625     *
18626     * @param {EventTarget~Event} [event]
18627     *        An event that triggered this function to run.
18628     *
18629     * @listens TextTrackList#addtrack
18630     * @listens TextTrackList#removetrack
18631     * @listens TextTrackList#change
18632     */
18633    ;
18634
18635    _proto.update = function update(event) {
18636      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
18637        this.setTrack(this.findChaptersTrack());
18638      }
18639
18640      _TextTrackButton.prototype.update.call(this);
18641    }
18642    /**
18643     * Set the currently selected track for the chapters button.
18644     *
18645     * @param {TextTrack} track
18646     *        The new track to select. Nothing will change if this is the currently selected
18647     *        track.
18648     */
18649    ;
18650
18651    _proto.setTrack = function setTrack(track) {
18652      if (this.track_ === track) {
18653        return;
18654      }
18655
18656      if (!this.updateHandler_) {
18657        this.updateHandler_ = this.update.bind(this);
18658      } // here this.track_ refers to the old track instance
18659
18660
18661      if (this.track_) {
18662        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18663
18664        if (remoteTextTrackEl) {
18665          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
18666        }
18667
18668        this.track_ = null;
18669      }
18670
18671      this.track_ = track; // here this.track_ refers to the new track instance
18672
18673      if (this.track_) {
18674        this.track_.mode = 'hidden';
18675
18676        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18677
18678        if (_remoteTextTrackEl) {
18679          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
18680        }
18681      }
18682    }
18683    /**
18684     * Find the track object that is currently in use by this ChaptersButton
18685     *
18686     * @return {TextTrack|undefined}
18687     *         The current track or undefined if none was found.
18688     */
18689    ;
18690
vendor: 15,561 bytes, lines 18691-19254
18691    _proto.findChaptersTrack = function findChaptersTrack() {
18692      var tracks = this.player_.textTracks() || [];
18693
18694      for (var i = tracks.length - 1; i >= 0; i--) {
18695        // We will always choose the last track as our chaptersTrack
18696        var track = tracks[i];
18697
18698        if (track.kind === this.kind_) {
18699          return track;
18700        }
18701      }
18702    }
18703    /**
18704     * Get the caption for the ChaptersButton based on the track label. This will also
18705     * use the current tracks localized kind as a fallback if a label does not exist.
18706     *
18707     * @return {string}
18708     *         The tracks current label or the localized track kind.
18709     */
18710    ;
18711
18712    _proto.getMenuCaption = function getMenuCaption() {
18713      if (this.track_ && this.track_.label) {
18714        return this.track_.label;
18715      }
18716
18717      return this.localize(toTitleCase(this.kind_));
18718    }
18719    /**
18720     * Create menu from chapter track
18721     *
18722     * @return {Menu}
18723     *         New menu for the chapter buttons
18724     */
18725    ;
18726
18727    _proto.createMenu = function createMenu() {
18728      this.options_.title = this.getMenuCaption();
18729      return _TextTrackButton.prototype.createMenu.call(this);
18730    }
18731    /**
18732     * Create a menu item for each text track
18733     *
18734     * @return {TextTrackMenuItem[]}
18735     *         Array of menu items
18736     */
18737    ;
18738
18739    _proto.createItems = function createItems() {
18740      var items = [];
18741
18742      if (!this.track_) {
18743        return items;
18744      }
18745
18746      var cues = this.track_.cues;
18747
18748      if (!cues) {
18749        return items;
18750      }
18751
18752      for (var i = 0, l = cues.length; i < l; i++) {
18753        var cue = cues[i];
18754        var mi = new ChaptersTrackMenuItem(this.player_, {
18755          track: this.track_,
18756          cue: cue
18757        });
18758        items.push(mi);
18759      }
18760
18761      return items;
18762    };
18763
18764    return ChaptersButton;
18765  }(TextTrackButton);
18766  /**
18767   * `kind` of TextTrack to look for to associate it with this menu.
18768   *
18769   * @type {string}
18770   * @private
18771   */
18772
18773
18774  ChaptersButton.prototype.kind_ = 'chapters';
18775  /**
18776   * The text that should display over the `ChaptersButton`s controls. Added for localization.
18777   *
18778   * @type {string}
18779   * @private
18780   */
18781
18782  ChaptersButton.prototype.controlText_ = 'Chapters';
18783  Component.registerComponent('ChaptersButton', ChaptersButton);
18784
18785  /**
18786   * The button component for toggling and selecting descriptions
18787   *
18788   * @extends TextTrackButton
18789   */
18790
18791  var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) {
18792    inheritsLoose(DescriptionsButton, _TextTrackButton);
18793
18794    /**
18795     * Creates an instance of this class.
18796     *
18797     * @param {Player} player
18798     *        The `Player` that this class should be attached to.
18799     *
18800     * @param {Object} [options]
18801     *        The key/value store of player options.
18802     *
18803     * @param {Component~ReadyCallback} [ready]
18804     *        The function to call when this component is ready.
18805     */
18806    function DescriptionsButton(player, options, ready) {
18807      var _this;
18808
18809      _this = _TextTrackButton.call(this, player, options, ready) || this;
18810      var tracks = player.textTracks();
18811      var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange);
18812      tracks.addEventListener('change', changeHandler);
18813
18814      _this.on('dispose', function () {
18815        tracks.removeEventListener('change', changeHandler);
18816      });
18817
18818      return _this;
18819    }
18820    /**
18821     * Handle text track change
18822     *
18823     * @param {EventTarget~Event} event
18824     *        The event that caused this function to run
18825     *
18826     * @listens TextTrackList#change
18827     */
18828
18829
18830    var _proto = DescriptionsButton.prototype;
18831
18832    _proto.handleTracksChange = function handleTracksChange(event) {
18833      var tracks = this.player().textTracks();
18834      var disabled = false; // Check whether a track of a different kind is showing
18835
18836      for (var i = 0, l = tracks.length; i < l; i++) {
18837        var track = tracks[i];
18838
18839        if (track.kind !== this.kind_ && track.mode === 'showing') {
18840          disabled = true;
18841          break;
18842        }
18843      } // If another track is showing, disable this menu button
18844
18845
18846      if (disabled) {
18847        this.disable();
18848      } else {
18849        this.enable();
18850      }
18851    }
18852    /**
18853     * Builds the default DOM `className`.
18854     *
18855     * @return {string}
18856     *         The DOM `className` for this object.
18857     */
18858    ;
18859
18860    _proto.buildCSSClass = function buildCSSClass() {
18861      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18862    };
18863
18864    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18865      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18866    };
18867
18868    return DescriptionsButton;
18869  }(TextTrackButton);
18870  /**
18871   * `kind` of TextTrack to look for to associate it with this menu.
18872   *
18873   * @type {string}
18874   * @private
18875   */
18876
18877
18878  DescriptionsButton.prototype.kind_ = 'descriptions';
18879  /**
18880   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
18881   *
18882   * @type {string}
18883   * @private
18884   */
18885
18886  DescriptionsButton.prototype.controlText_ = 'Descriptions';
18887  Component.registerComponent('DescriptionsButton', DescriptionsButton);
18888
18889  /**
18890   * The button component for toggling and selecting subtitles
18891   *
18892   * @extends TextTrackButton
18893   */
18894
18895  var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) {
18896    inheritsLoose(SubtitlesButton, _TextTrackButton);
18897
18898    /**
18899     * Creates an instance of this class.
18900     *
18901     * @param {Player} player
18902     *        The `Player` that this class should be attached to.
18903     *
18904     * @param {Object} [options]
18905     *        The key/value store of player options.
18906     *
18907     * @param {Component~ReadyCallback} [ready]
18908     *        The function to call when this component is ready.
18909     */
18910    function SubtitlesButton(player, options, ready) {
18911      return _TextTrackButton.call(this, player, options, ready) || this;
18912    }
18913    /**
18914     * Builds the default DOM `className`.
18915     *
18916     * @return {string}
18917     *         The DOM `className` for this object.
18918     */
18919
18920
18921    var _proto = SubtitlesButton.prototype;
18922
18923    _proto.buildCSSClass = function buildCSSClass() {
18924      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18925    };
18926
18927    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18928      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18929    };
18930
18931    return SubtitlesButton;
18932  }(TextTrackButton);
18933  /**
18934   * `kind` of TextTrack to look for to associate it with this menu.
18935   *
18936   * @type {string}
18937   * @private
18938   */
18939
18940
18941  SubtitlesButton.prototype.kind_ = 'subtitles';
18942  /**
18943   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
18944   *
18945   * @type {string}
18946   * @private
18947   */
18948
18949  SubtitlesButton.prototype.controlText_ = 'Subtitles';
18950  Component.registerComponent('SubtitlesButton', SubtitlesButton);
18951
18952  /**
18953   * The menu item for caption track settings menu
18954   *
18955   * @extends TextTrackMenuItem
18956   */
18957
18958  var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
18959    inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
18960
18961    /**
18962     * Creates an instance of this class.
18963     *
18964     * @param {Player} player
18965     *        The `Player` that this class should be attached to.
18966     *
18967     * @param {Object} [options]
18968     *        The key/value store of player options.
18969     */
18970    function CaptionSettingsMenuItem(player, options) {
18971      var _this;
18972
18973      options.track = {
18974        player: player,
18975        kind: options.kind,
18976        label: options.kind + ' settings',
18977        selectable: false,
18978        "default": false,
18979        mode: 'disabled'
18980      }; // CaptionSettingsMenuItem has no concept of 'selected'
18981
18982      options.selectable = false;
18983      options.name = 'CaptionSettingsMenuItem';
18984      _this = _TextTrackMenuItem.call(this, player, options) || this;
18985
18986      _this.addClass('vjs-texttrack-settings');
18987
18988      _this.controlText(', opens ' + options.kind + ' settings dialog');
18989
18990      return _this;
18991    }
18992    /**
18993     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
18994     * {@link ClickableComponent} for more detailed information on what a click can be.
18995     *
18996     * @param {EventTarget~Event} [event]
18997     *        The `keydown`, `tap`, or `click` event that caused this function to be
18998     *        called.
18999     *
19000     * @listens tap
19001     * @listens click
19002     */
19003
19004
19005    var _proto = CaptionSettingsMenuItem.prototype;
19006
19007    _proto.handleClick = function handleClick(event) {
19008      this.player().getChild('textTrackSettings').open();
19009    };
19010
19011    return CaptionSettingsMenuItem;
19012  }(TextTrackMenuItem);
19013
19014  Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
19015
19016  /**
19017   * The button component for toggling and selecting captions
19018   *
19019   * @extends TextTrackButton
19020   */
19021
19022  var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) {
19023    inheritsLoose(CaptionsButton, _TextTrackButton);
19024
19025    /**
19026     * Creates an instance of this class.
19027     *
19028     * @param {Player} player
19029     *        The `Player` that this class should be attached to.
19030     *
19031     * @param {Object} [options]
19032     *        The key/value store of player options.
19033     *
19034     * @param {Component~ReadyCallback} [ready]
19035     *        The function to call when this component is ready.
19036     */
19037    function CaptionsButton(player, options, ready) {
19038      return _TextTrackButton.call(this, player, options, ready) || this;
19039    }
19040    /**
19041     * Builds the default DOM `className`.
19042     *
19043     * @return {string}
19044     *         The DOM `className` for this object.
19045     */
19046
19047
19048    var _proto = CaptionsButton.prototype;
19049
19050    _proto.buildCSSClass = function buildCSSClass() {
19051      return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19052    };
19053
19054    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19055      return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19056    }
19057    /**
19058     * Create caption menu items
19059     *
19060     * @return {CaptionSettingsMenuItem[]}
19061     *         The array of current menu items.
19062     */
19063    ;
19064
19065    _proto.createItems = function createItems() {
19066      var items = [];
19067
19068      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
19069        items.push(new CaptionSettingsMenuItem(this.player_, {
19070          kind: this.kind_
19071        }));
19072        this.hideThreshold_ += 1;
19073      }
19074
19075      return _TextTrackButton.prototype.createItems.call(this, items);
19076    };
19077
19078    return CaptionsButton;
19079  }(TextTrackButton);
19080  /**
19081   * `kind` of TextTrack to look for to associate it with this menu.
19082   *
19083   * @type {string}
19084   * @private
19085   */
19086
19087
19088  CaptionsButton.prototype.kind_ = 'captions';
19089  /**
19090   * The text that should display over the `CaptionsButton`s controls. Added for localization.
19091   *
19092   * @type {string}
19093   * @private
19094   */
19095
19096  CaptionsButton.prototype.controlText_ = 'Captions';
19097  Component.registerComponent('CaptionsButton', CaptionsButton);
19098
19099  /**
19100   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
19101   * in the SubsCapsMenu.
19102   *
19103   * @extends TextTrackMenuItem
19104   */
19105
19106  var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
19107    inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
19108
19109    function SubsCapsMenuItem() {
19110      return _TextTrackMenuItem.apply(this, arguments) || this;
19111    }
19112
19113    var _proto = SubsCapsMenuItem.prototype;
19114
19115    _proto.createEl = function createEl(type, props, attrs) {
19116      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
19117
19118      if (this.options_.track.kind === 'captions') {
19119        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n      ";
19120      }
19121
19122      innerHTML += '</span>';
19123
19124      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
19125        innerHTML: innerHTML
19126      }, props), attrs);
19127
19128      return el;
19129    };
19130
19131    return SubsCapsMenuItem;
19132  }(TextTrackMenuItem);
19133
19134  Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
19135
19136  /**
19137   * The button component for toggling and selecting captions and/or subtitles
19138   *
19139   * @extends TextTrackButton
19140   */
19141
19142  var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) {
19143    inheritsLoose(SubsCapsButton, _TextTrackButton);
19144
19145    function SubsCapsButton(player, options) {
19146      var _this;
19147
19148      if (options === void 0) {
19149        options = {};
19150      }
19151
19152      _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
19153      // "captions and subtitles" other locales use "subtitles"
19154
19155      _this.label_ = 'subtitles';
19156
19157      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
19158        _this.label_ = 'captions';
19159      }
19160
19161      _this.menuButton_.controlText(toTitleCase(_this.label_));
19162
19163      return _this;
19164    }
19165    /**
19166     * Builds the default DOM `className`.
19167     *
19168     * @return {string}
19169     *         The DOM `className` for this object.
19170     */
19171
19172
19173    var _proto = SubsCapsButton.prototype;
19174
19175    _proto.buildCSSClass = function buildCSSClass() {
19176      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19177    };
19178
19179    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19180      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19181    }
19182    /**
19183     * Create caption/subtitles menu items
19184     *
19185     * @return {CaptionSettingsMenuItem[]}
19186     *         The array of current menu items.
19187     */
19188    ;
19189
19190    _proto.createItems = function createItems() {
19191      var items = [];
19192
19193      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
19194        items.push(new CaptionSettingsMenuItem(this.player_, {
19195          kind: this.label_
19196        }));
19197        this.hideThreshold_ += 1;
19198      }
19199
19200      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
19201      return items;
19202    };
19203
19204    return SubsCapsButton;
19205  }(TextTrackButton);
19206  /**
19207   * `kind`s of TextTrack to look for to associate it with this menu.
19208   *
19209   * @type {array}
19210   * @private
19211   */
19212
19213
19214  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
19215  /**
19216   * The text that should display over the `SubsCapsButton`s controls.
19217   *
19218   *
19219   * @type {string}
19220   * @private
19221   */
19222
19223  SubsCapsButton.prototype.controlText_ = 'Subtitles';
19224  Component.registerComponent('SubsCapsButton', SubsCapsButton);
19225
19226  /**
19227   * An {@link AudioTrack} {@link MenuItem}
19228   *
19229   * @extends MenuItem
19230   */
19231
19232  var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
19233    inheritsLoose(AudioTrackMenuItem, _MenuItem);
19234
19235    /**
19236     * Creates an instance of this class.
19237     *
19238     * @param {Player} player
19239     *        The `Player` that this class should be attached to.
19240     *
19241     * @param {Object} [options]
19242     *        The key/value store of player options.
19243     */
19244    function AudioTrackMenuItem(player, options) {
19245      var _this;
19246
19247      var track = options.track;
19248      var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
19249
19250      options.label = track.label || track.language || 'Unknown';
19251      options.selected = track.enabled;
19252      _this = _MenuItem.call(this, player, options) || this;
19253      _this.track = track;
19254
vendor: 5,647 bytes, lines 19255-19450
19255      _this.addClass("vjs-" + track.kind + "-menu-item");
19256
19257      var changeHandler = function changeHandler() {
19258        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
19259          args[_key] = arguments[_key];
19260        }
19261
19262        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
19263      };
19264
19265      tracks.addEventListener('change', changeHandler);
19266
19267      _this.on('dispose', function () {
19268        tracks.removeEventListener('change', changeHandler);
19269      });
19270
19271      return _this;
19272    }
19273
19274    var _proto = AudioTrackMenuItem.prototype;
19275
19276    _proto.createEl = function createEl(type, props, attrs) {
19277      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
19278
19279      if (this.options_.track.kind === 'main-desc') {
19280        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n      ";
19281      }
19282
19283      innerHTML += '</span>';
19284
19285      var el = _MenuItem.prototype.createEl.call(this, type, assign({
19286        innerHTML: innerHTML
19287      }, props), attrs);
19288
19289      return el;
19290    }
19291    /**
19292     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
19293     * for more detailed information on what a click can be.
19294     *
19295     * @param {EventTarget~Event} [event]
19296     *        The `keydown`, `tap`, or `click` event that caused this function to be
19297     *        called.
19298     *
19299     * @listens tap
19300     * @listens click
19301     */
19302    ;
19303
19304    _proto.handleClick = function handleClick(event) {
19305      var tracks = this.player_.audioTracks();
19306
19307      _MenuItem.prototype.handleClick.call(this, event);
19308
19309      for (var i = 0; i < tracks.length; i++) {
19310        var track = tracks[i];
19311        track.enabled = track === this.track;
19312      }
19313    }
19314    /**
19315     * Handle any {@link AudioTrack} change.
19316     *
19317     * @param {EventTarget~Event} [event]
19318     *        The {@link AudioTrackList#change} event that caused this to run.
19319     *
19320     * @listens AudioTrackList#change
19321     */
19322    ;
19323
19324    _proto.handleTracksChange = function handleTracksChange(event) {
19325      this.selected(this.track.enabled);
19326    };
19327
19328    return AudioTrackMenuItem;
19329  }(MenuItem);
19330
19331  Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
19332
19333  /**
19334   * The base class for buttons that toggle specific {@link AudioTrack} types.
19335   *
19336   * @extends TrackButton
19337   */
19338
19339  var AudioTrackButton = /*#__PURE__*/function (_TrackButton) {
19340    inheritsLoose(AudioTrackButton, _TrackButton);
19341
19342    /**
19343     * Creates an instance of this class.
19344     *
19345     * @param {Player} player
19346     *        The `Player` that this class should be attached to.
19347     *
19348     * @param {Object} [options={}]
19349     *        The key/value store of player options.
19350     */
19351    function AudioTrackButton(player, options) {
19352      if (options === void 0) {
19353        options = {};
19354      }
19355
19356      options.tracks = player.audioTracks();
19357      return _TrackButton.call(this, player, options) || this;
19358    }
19359    /**
19360     * Builds the default DOM `className`.
19361     *
19362     * @return {string}
19363     *         The DOM `className` for this object.
19364     */
19365
19366
19367    var _proto = AudioTrackButton.prototype;
19368
19369    _proto.buildCSSClass = function buildCSSClass() {
19370      return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
19371    };
19372
19373    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19374      return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
19375    }
19376    /**
19377     * Create a menu item for each audio track
19378     *
19379     * @param {AudioTrackMenuItem[]} [items=[]]
19380     *        An array of existing menu items to use.
19381     *
19382     * @return {AudioTrackMenuItem[]}
19383     *         An array of menu items
19384     */
19385    ;
19386
19387    _proto.createItems = function createItems(items) {
19388      if (items === void 0) {
19389        items = [];
19390      }
19391
19392      // if there's only one audio track, there no point in showing it
19393      this.hideThreshold_ = 1;
19394      var tracks = this.player_.audioTracks();
19395
19396      for (var i = 0; i < tracks.length; i++) {
19397        var track = tracks[i];
19398        items.push(new AudioTrackMenuItem(this.player_, {
19399          track: track,
19400          // MenuItem is selectable
19401          selectable: true,
19402          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
19403          multiSelectable: false
19404        }));
19405      }
19406
19407      return items;
19408    };
19409
19410    return AudioTrackButton;
19411  }(TrackButton);
19412  /**
19413   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
19414   *
19415   * @type {string}
19416   * @private
19417   */
19418
19419
19420  AudioTrackButton.prototype.controlText_ = 'Audio Track';
19421  Component.registerComponent('AudioTrackButton', AudioTrackButton);
19422
19423  /**
19424   * The specific menu item type for selecting a playback rate.
19425   *
19426   * @extends MenuItem
19427   */
19428
19429  var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) {
19430    inheritsLoose(PlaybackRateMenuItem, _MenuItem);
19431
19432    /**
19433     * Creates an instance of this class.
19434     *
19435     * @param {Player} player
19436     *        The `Player` that this class should be attached to.
19437     *
19438     * @param {Object} [options]
19439     *        The key/value store of player options.
19440     */
19441    function PlaybackRateMenuItem(player, options) {
19442      var _this;
19443
19444      var label = options.rate;
19445      var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
19446
19447      options.label = label;
19448      options.selected = rate === 1;
19449      options.selectable = true;
19450      options.multiSelectable = false;
vendor: 110,920 bytes, lines 19451-22976
19451      _this = _MenuItem.call(this, player, options) || this;
19452      _this.label = label;
19453      _this.rate = rate;
19454
19455      _this.on(player, 'ratechange', _this.update);
19456
19457      return _this;
19458    }
19459    /**
19460     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
19461     * {@link ClickableComponent} for more detailed information on what a click can be.
19462     *
19463     * @param {EventTarget~Event} [event]
19464     *        The `keydown`, `tap`, or `click` event that caused this function to be
19465     *        called.
19466     *
19467     * @listens tap
19468     * @listens click
19469     */
19470
19471
19472    var _proto = PlaybackRateMenuItem.prototype;
19473
19474    _proto.handleClick = function handleClick(event) {
19475      _MenuItem.prototype.handleClick.call(this);
19476
19477      this.player().playbackRate(this.rate);
19478    }
19479    /**
19480     * Update the PlaybackRateMenuItem when the playbackrate changes.
19481     *
19482     * @param {EventTarget~Event} [event]
19483     *        The `ratechange` event that caused this function to run.
19484     *
19485     * @listens Player#ratechange
19486     */
19487    ;
19488
19489    _proto.update = function update(event) {
19490      this.selected(this.player().playbackRate() === this.rate);
19491    };
19492
19493    return PlaybackRateMenuItem;
19494  }(MenuItem);
19495  /**
19496   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
19497   *
19498   * @type {string}
19499   * @private
19500   */
19501
19502
19503  PlaybackRateMenuItem.prototype.contentElType = 'button';
19504  Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
19505
19506  /**
19507   * The component for controlling the playback rate.
19508   *
19509   * @extends MenuButton
19510   */
19511
19512  var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) {
19513    inheritsLoose(PlaybackRateMenuButton, _MenuButton);
19514
19515    /**
19516     * Creates an instance of this class.
19517     *
19518     * @param {Player} player
19519     *        The `Player` that this class should be attached to.
19520     *
19521     * @param {Object} [options]
19522     *        The key/value store of player options.
19523     */
19524    function PlaybackRateMenuButton(player, options) {
19525      var _this;
19526
19527      _this = _MenuButton.call(this, player, options) || this;
19528
19529      _this.updateVisibility();
19530
19531      _this.updateLabel();
19532
19533      _this.on(player, 'loadstart', _this.updateVisibility);
19534
19535      _this.on(player, 'ratechange', _this.updateLabel);
19536
19537      return _this;
19538    }
19539    /**
19540     * Create the `Component`'s DOM element
19541     *
19542     * @return {Element}
19543     *         The element that was created.
19544     */
19545
19546
19547    var _proto = PlaybackRateMenuButton.prototype;
19548
19549    _proto.createEl = function createEl$1() {
19550      var el = _MenuButton.prototype.createEl.call(this);
19551
19552      this.labelEl_ = createEl('div', {
19553        className: 'vjs-playback-rate-value',
19554        innerHTML: '1x'
19555      });
19556      el.appendChild(this.labelEl_);
19557      return el;
19558    };
19559
19560    _proto.dispose = function dispose() {
19561      this.labelEl_ = null;
19562
19563      _MenuButton.prototype.dispose.call(this);
19564    }
19565    /**
19566     * Builds the default DOM `className`.
19567     *
19568     * @return {string}
19569     *         The DOM `className` for this object.
19570     */
19571    ;
19572
19573    _proto.buildCSSClass = function buildCSSClass() {
19574      return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
19575    };
19576
19577    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19578      return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
19579    }
19580    /**
19581     * Create the playback rate menu
19582     *
19583     * @return {Menu}
19584     *         Menu object populated with {@link PlaybackRateMenuItem}s
19585     */
19586    ;
19587
19588    _proto.createMenu = function createMenu() {
19589      var menu = new Menu(this.player());
19590      var rates = this.playbackRates();
19591
19592      if (rates) {
19593        for (var i = rates.length - 1; i >= 0; i--) {
19594          menu.addChild(new PlaybackRateMenuItem(this.player(), {
19595            rate: rates[i] + 'x'
19596          }));
19597        }
19598      }
19599
19600      return menu;
19601    }
19602    /**
19603     * Updates ARIA accessibility attributes
19604     */
19605    ;
19606
19607    _proto.updateARIAAttributes = function updateARIAAttributes() {
19608      // Current playback rate
19609      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
19610    }
19611    /**
19612     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
19613     * {@link ClickableComponent} for more detailed information on what a click can be.
19614     *
19615     * @param {EventTarget~Event} [event]
19616     *        The `keydown`, `tap`, or `click` event that caused this function to be
19617     *        called.
19618     *
19619     * @listens tap
19620     * @listens click
19621     */
19622    ;
19623
19624    _proto.handleClick = function handleClick(event) {
19625      // select next rate option
19626      var currentRate = this.player().playbackRate();
19627      var rates = this.playbackRates(); // this will select first one if the last one currently selected
19628
19629      var newRate = rates[0];
19630
19631      for (var i = 0; i < rates.length; i++) {
19632        if (rates[i] > currentRate) {
19633          newRate = rates[i];
19634          break;
19635        }
19636      }
19637
19638      this.player().playbackRate(newRate);
19639    }
19640    /**
19641     * Get possible playback rates
19642     *
19643     * @return {Array}
19644     *         All possible playback rates
19645     */
19646    ;
19647
19648    _proto.playbackRates = function playbackRates() {
19649      return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
19650    }
19651    /**
19652     * Get whether playback rates is supported by the tech
19653     * and an array of playback rates exists
19654     *
19655     * @return {boolean}
19656     *         Whether changing playback rate is supported
19657     */
19658    ;
19659
19660    _proto.playbackRateSupported = function playbackRateSupported() {
19661      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
19662    }
19663    /**
19664     * Hide playback rate controls when they're no playback rate options to select
19665     *
19666     * @param {EventTarget~Event} [event]
19667     *        The event that caused this function to run.
19668     *
19669     * @listens Player#loadstart
19670     */
19671    ;
19672
19673    _proto.updateVisibility = function updateVisibility(event) {
19674      if (this.playbackRateSupported()) {
19675        this.removeClass('vjs-hidden');
19676      } else {
19677        this.addClass('vjs-hidden');
19678      }
19679    }
19680    /**
19681     * Update button label when rate changed
19682     *
19683     * @param {EventTarget~Event} [event]
19684     *        The event that caused this function to run.
19685     *
19686     * @listens Player#ratechange
19687     */
19688    ;
19689
19690    _proto.updateLabel = function updateLabel(event) {
19691      if (this.playbackRateSupported()) {
19692        this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
19693      }
19694    };
19695
19696    return PlaybackRateMenuButton;
19697  }(MenuButton);
19698  /**
19699   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
19700   *
19701   * @type {string}
19702   * @private
19703   */
19704
19705
19706  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
19707  Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
19708
19709  /**
19710   * Just an empty spacer element that can be used as an append point for plugins, etc.
19711   * Also can be used to create space between elements when necessary.
19712   *
19713   * @extends Component
19714   */
19715
19716  var Spacer = /*#__PURE__*/function (_Component) {
19717    inheritsLoose(Spacer, _Component);
19718
19719    function Spacer() {
19720      return _Component.apply(this, arguments) || this;
19721    }
19722
19723    var _proto = Spacer.prototype;
19724
19725    /**
19726    * Builds the default DOM `className`.
19727    *
19728    * @return {string}
19729    *         The DOM `className` for this object.
19730    */
19731    _proto.buildCSSClass = function buildCSSClass() {
19732      return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
19733    }
19734    /**
19735     * Create the `Component`'s DOM element
19736     *
19737     * @return {Element}
19738     *         The element that was created.
19739     */
19740    ;
19741
19742    _proto.createEl = function createEl() {
19743      return _Component.prototype.createEl.call(this, 'div', {
19744        className: this.buildCSSClass()
19745      });
19746    };
19747
19748    return Spacer;
19749  }(Component);
19750
19751  Component.registerComponent('Spacer', Spacer);
19752
19753  /**
19754   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
19755   *
19756   * @extends Spacer
19757   */
19758
19759  var CustomControlSpacer = /*#__PURE__*/function (_Spacer) {
19760    inheritsLoose(CustomControlSpacer, _Spacer);
19761
19762    function CustomControlSpacer() {
19763      return _Spacer.apply(this, arguments) || this;
19764    }
19765
19766    var _proto = CustomControlSpacer.prototype;
19767
19768    /**
19769     * Builds the default DOM `className`.
19770     *
19771     * @return {string}
19772     *         The DOM `className` for this object.
19773     */
19774    _proto.buildCSSClass = function buildCSSClass() {
19775      return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
19776    }
19777    /**
19778     * Create the `Component`'s DOM element
19779     *
19780     * @return {Element}
19781     *         The element that was created.
19782     */
19783    ;
19784
19785    _proto.createEl = function createEl() {
19786      var el = _Spacer.prototype.createEl.call(this, {
19787        className: this.buildCSSClass()
19788      }); // No-flex/table-cell mode requires there be some content
19789      // in the cell to fill the remaining space of the table.
19790
19791
19792      el.innerHTML = "\xA0";
19793      return el;
19794    };
19795
19796    return CustomControlSpacer;
19797  }(Spacer);
19798
19799  Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
19800
19801  /**
19802   * Container of main controls.
19803   *
19804   * @extends Component
19805   */
19806
19807  var ControlBar = /*#__PURE__*/function (_Component) {
19808    inheritsLoose(ControlBar, _Component);
19809
19810    function ControlBar() {
19811      return _Component.apply(this, arguments) || this;
19812    }
19813
19814    var _proto = ControlBar.prototype;
19815
19816    /**
19817     * Create the `Component`'s DOM element
19818     *
19819     * @return {Element}
19820     *         The element that was created.
19821     */
19822    _proto.createEl = function createEl() {
19823      return _Component.prototype.createEl.call(this, 'div', {
19824        className: 'vjs-control-bar',
19825        dir: 'ltr'
19826      });
19827    };
19828
19829    return ControlBar;
19830  }(Component);
19831  /**
19832   * Default options for `ControlBar`
19833   *
19834   * @type {Object}
19835   * @private
19836   */
19837
19838
19839  ControlBar.prototype.options_ = {
19840    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
19841  };
19842
19843  if ('exitPictureInPicture' in document) {
19844    ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle');
19845  }
19846
19847  Component.registerComponent('ControlBar', ControlBar);
19848
19849  /**
19850   * A display that indicates an error has occurred. This means that the video
19851   * is unplayable.
19852   *
19853   * @extends ModalDialog
19854   */
19855
19856  var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) {
19857    inheritsLoose(ErrorDisplay, _ModalDialog);
19858
19859    /**
19860     * Creates an instance of this class.
19861     *
19862     * @param  {Player} player
19863     *         The `Player` that this class should be attached to.
19864     *
19865     * @param  {Object} [options]
19866     *         The key/value store of player options.
19867     */
19868    function ErrorDisplay(player, options) {
19869      var _this;
19870
19871      _this = _ModalDialog.call(this, player, options) || this;
19872
19873      _this.on(player, 'error', _this.open);
19874
19875      return _this;
19876    }
19877    /**
19878     * Builds the default DOM `className`.
19879     *
19880     * @return {string}
19881     *         The DOM `className` for this object.
19882     *
19883     * @deprecated Since version 5.
19884     */
19885
19886
19887    var _proto = ErrorDisplay.prototype;
19888
19889    _proto.buildCSSClass = function buildCSSClass() {
19890      return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
19891    }
19892    /**
19893     * Gets the localized error message based on the `Player`s error.
19894     *
19895     * @return {string}
19896     *         The `Player`s error message localized or an empty string.
19897     */
19898    ;
19899
19900    _proto.content = function content() {
19901      var error = this.player().error();
19902      return error ? this.localize(error.message) : '';
19903    };
19904
19905    return ErrorDisplay;
19906  }(ModalDialog);
19907  /**
19908   * The default options for an `ErrorDisplay`.
19909   *
19910   * @private
19911   */
19912
19913
19914  ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
19915    pauseOnOpen: false,
19916    fillAlways: true,
19917    temporary: false,
19918    uncloseable: true
19919  });
19920  Component.registerComponent('ErrorDisplay', ErrorDisplay);
19921
19922  var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
19923  var COLOR_BLACK = ['#000', 'Black'];
19924  var COLOR_BLUE = ['#00F', 'Blue'];
19925  var COLOR_CYAN = ['#0FF', 'Cyan'];
19926  var COLOR_GREEN = ['#0F0', 'Green'];
19927  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
19928  var COLOR_RED = ['#F00', 'Red'];
19929  var COLOR_WHITE = ['#FFF', 'White'];
19930  var COLOR_YELLOW = ['#FF0', 'Yellow'];
19931  var OPACITY_OPAQUE = ['1', 'Opaque'];
19932  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
19933  var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
19934  //
19935  // Possible keys include:
19936  //
19937  // `default`:
19938  //   The default option index. Only needs to be provided if not zero.
19939  // `parser`:
19940  //   A function which is used to parse the value from the selected option in
19941  //   a customized way.
19942  // `selector`:
19943  //   The selector used to find the associated <select> element.
19944
19945  var selectConfigs = {
19946    backgroundColor: {
19947      selector: '.vjs-bg-color > select',
19948      id: 'captions-background-color-%s',
19949      label: 'Color',
19950      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19951    },
19952    backgroundOpacity: {
19953      selector: '.vjs-bg-opacity > select',
19954      id: 'captions-background-opacity-%s',
19955      label: 'Transparency',
19956      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
19957    },
19958    color: {
19959      selector: '.vjs-fg-color > select',
19960      id: 'captions-foreground-color-%s',
19961      label: 'Color',
19962      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19963    },
19964    edgeStyle: {
19965      selector: '.vjs-edge-style > select',
19966      id: '%s',
19967      label: 'Text Edge Style',
19968      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
19969    },
19970    fontFamily: {
19971      selector: '.vjs-font-family > select',
19972      id: 'captions-font-family-%s',
19973      label: 'Font Family',
19974      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
19975    },
19976    fontPercent: {
19977      selector: '.vjs-font-percent > select',
19978      id: 'captions-font-size-%s',
19979      label: 'Font Size',
19980      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%']],
19981      "default": 2,
19982      parser: function parser(v) {
19983        return v === '1.00' ? null : Number(v);
19984      }
19985    },
19986    textOpacity: {
19987      selector: '.vjs-text-opacity > select',
19988      id: 'captions-foreground-opacity-%s',
19989      label: 'Transparency',
19990      options: [OPACITY_OPAQUE, OPACITY_SEMI]
19991    },
19992    // Options for this object are defined below.
19993    windowColor: {
19994      selector: '.vjs-window-color > select',
19995      id: 'captions-window-color-%s',
19996      label: 'Color'
19997    },
19998    // Options for this object are defined below.
19999    windowOpacity: {
20000      selector: '.vjs-window-opacity > select',
20001      id: 'captions-window-opacity-%s',
20002      label: 'Transparency',
20003      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
20004    }
20005  };
20006  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
20007  /**
20008   * Get the actual value of an option.
20009   *
20010   * @param  {string} value
20011   *         The value to get
20012   *
20013   * @param  {Function} [parser]
20014   *         Optional function to adjust the value.
20015   *
20016   * @return {Mixed}
20017   *         - Will be `undefined` if no value exists
20018   *         - Will be `undefined` if the given value is "none".
20019   *         - Will be the actual value otherwise.
20020   *
20021   * @private
20022   */
20023
20024  function parseOptionValue(value, parser) {
20025    if (parser) {
20026      value = parser(value);
20027    }
20028
20029    if (value && value !== 'none') {
20030      return value;
20031    }
20032  }
20033  /**
20034   * Gets the value of the selected <option> element within a <select> element.
20035   *
20036   * @param  {Element} el
20037   *         the element to look in
20038   *
20039   * @param  {Function} [parser]
20040   *         Optional function to adjust the value.
20041   *
20042   * @return {Mixed}
20043   *         - Will be `undefined` if no value exists
20044   *         - Will be `undefined` if the given value is "none".
20045   *         - Will be the actual value otherwise.
20046   *
20047   * @private
20048   */
20049
20050
20051  function getSelectedOptionValue(el, parser) {
20052    var value = el.options[el.options.selectedIndex].value;
20053    return parseOptionValue(value, parser);
20054  }
20055  /**
20056   * Sets the selected <option> element within a <select> element based on a
20057   * given value.
20058   *
20059   * @param {Element} el
20060   *        The element to look in.
20061   *
20062   * @param {string} value
20063   *        the property to look on.
20064   *
20065   * @param {Function} [parser]
20066   *        Optional function to adjust the value before comparing.
20067   *
20068   * @private
20069   */
20070
20071
20072  function setSelectedOption(el, value, parser) {
20073    if (!value) {
20074      return;
20075    }
20076
20077    for (var i = 0; i < el.options.length; i++) {
20078      if (parseOptionValue(el.options[i].value, parser) === value) {
20079        el.selectedIndex = i;
20080        break;
20081      }
20082    }
20083  }
20084  /**
20085   * Manipulate Text Tracks settings.
20086   *
20087   * @extends ModalDialog
20088   */
20089
20090
20091  var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) {
20092    inheritsLoose(TextTrackSettings, _ModalDialog);
20093
20094    /**
20095     * Creates an instance of this class.
20096     *
20097     * @param {Player} player
20098     *         The `Player` that this class should be attached to.
20099     *
20100     * @param {Object} [options]
20101     *         The key/value store of player options.
20102     */
20103    function TextTrackSettings(player, options) {
20104      var _this;
20105
20106      options.temporary = false;
20107      _this = _ModalDialog.call(this, player, options) || this;
20108      _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it
20109
20110      _this.fill();
20111
20112      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
20113      _this.endDialog = createEl('p', {
20114        className: 'vjs-control-text',
20115        textContent: _this.localize('End of dialog window.')
20116      });
20117
20118      _this.el().appendChild(_this.endDialog);
20119
20120      _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
20121
20122
20123      if (options.persistTextTrackSettings === undefined) {
20124        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
20125      }
20126
20127      _this.on(_this.$('.vjs-done-button'), 'click', function () {
20128        _this.saveSettings();
20129
20130        _this.close();
20131      });
20132
20133      _this.on(_this.$('.vjs-default-button'), 'click', function () {
20134        _this.setDefaults();
20135
20136        _this.updateDisplay();
20137      });
20138
20139      each(selectConfigs, function (config) {
20140        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
20141      });
20142
20143      if (_this.options_.persistTextTrackSettings) {
20144        _this.restoreSettings();
20145      }
20146
20147      return _this;
20148    }
20149
20150    var _proto = TextTrackSettings.prototype;
20151
20152    _proto.dispose = function dispose() {
20153      this.endDialog = null;
20154
20155      _ModalDialog.prototype.dispose.call(this);
20156    }
20157    /**
20158     * Create a <select> element with configured options.
20159     *
20160     * @param {string} key
20161     *        Configuration key to use during creation.
20162     *
20163     * @return {string}
20164     *         An HTML string.
20165     *
20166     * @private
20167     */
20168    ;
20169
20170    _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
20171      var _this2 = this;
20172
20173      if (legendId === void 0) {
20174        legendId = '';
20175      }
20176
20177      if (type === void 0) {
20178        type = 'label';
20179      }
20180
20181      var config = selectConfigs[key];
20182      var id = config.id.replace('%s', this.id_);
20183      var selectLabelledbyIds = [legendId, id].join(' ').trim();
20184      return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
20185        var optionId = id + '-' + o[1].replace(/\W+/g, '');
20186        return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
20187      })).concat('</select>').join('');
20188    }
20189    /**
20190     * Create foreground color element for the component
20191     *
20192     * @return {string}
20193     *         An HTML string.
20194     *
20195     * @private
20196     */
20197    ;
20198
20199    _proto.createElFgColor_ = function createElFgColor_() {
20200      var legendId = "captions-text-legend-" + this.id_;
20201      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('');
20202    }
20203    /**
20204     * Create background color element for the component
20205     *
20206     * @return {string}
20207     *         An HTML string.
20208     *
20209     * @private
20210     */
20211    ;
20212
20213    _proto.createElBgColor_ = function createElBgColor_() {
20214      var legendId = "captions-background-" + this.id_;
20215      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('');
20216    }
20217    /**
20218     * Create window color element for the component
20219     *
20220     * @return {string}
20221     *         An HTML string.
20222     *
20223     * @private
20224     */
20225    ;
20226
20227    _proto.createElWinColor_ = function createElWinColor_() {
20228      var legendId = "captions-window-" + this.id_;
20229      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('');
20230    }
20231    /**
20232     * Create color elements for the component
20233     *
20234     * @return {Element}
20235     *         The element that was created
20236     *
20237     * @private
20238     */
20239    ;
20240
20241    _proto.createElColors_ = function createElColors_() {
20242      return createEl('div', {
20243        className: 'vjs-track-settings-colors',
20244        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
20245      });
20246    }
20247    /**
20248     * Create font elements for the component
20249     *
20250     * @return {Element}
20251     *         The element that was created.
20252     *
20253     * @private
20254     */
20255    ;
20256
20257    _proto.createElFont_ = function createElFont_() {
20258      return createEl('div', {
20259        className: 'vjs-track-settings-font',
20260        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('')
20261      });
20262    }
20263    /**
20264     * Create controls for the component
20265     *
20266     * @return {Element}
20267     *         The element that was created.
20268     *
20269     * @private
20270     */
20271    ;
20272
20273    _proto.createElControls_ = function createElControls_() {
20274      var defaultsDescription = this.localize('restore all settings to the default values');
20275      return createEl('div', {
20276        className: 'vjs-track-settings-controls',
20277        innerHTML: ["<button type=\"button\" class=\"vjs-default-button\" title=\"" + defaultsDescription + "\">", this.localize('Reset'), "<span class=\"vjs-control-text\"> " + defaultsDescription + "</span>", '</button>', "<button type=\"button\" class=\"vjs-done-button\">" + this.localize('Done') + "</button>"].join('')
20278      });
20279    };
20280
20281    _proto.content = function content() {
20282      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
20283    };
20284
20285    _proto.label = function label() {
20286      return this.localize('Caption Settings Dialog');
20287    };
20288
20289    _proto.description = function description() {
20290      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
20291    };
20292
20293    _proto.buildCSSClass = function buildCSSClass() {
20294      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
20295    }
20296    /**
20297     * Gets an object of text track settings (or null).
20298     *
20299     * @return {Object}
20300     *         An object with config values parsed from the DOM or localStorage.
20301     */
20302    ;
20303
20304    _proto.getValues = function getValues() {
20305      var _this3 = this;
20306
20307      return reduce(selectConfigs, function (accum, config, key) {
20308        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
20309
20310        if (value !== undefined) {
20311          accum[key] = value;
20312        }
20313
20314        return accum;
20315      }, {});
20316    }
20317    /**
20318     * Sets text track settings from an object of values.
20319     *
20320     * @param {Object} values
20321     *        An object with config values parsed from the DOM or localStorage.
20322     */
20323    ;
20324
20325    _proto.setValues = function setValues(values) {
20326      var _this4 = this;
20327
20328      each(selectConfigs, function (config, key) {
20329        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
20330      });
20331    }
20332    /**
20333     * Sets all `<select>` elements to their default values.
20334     */
20335    ;
20336
20337    _proto.setDefaults = function setDefaults() {
20338      var _this5 = this;
20339
20340      each(selectConfigs, function (config) {
20341        var index = config.hasOwnProperty('default') ? config["default"] : 0;
20342        _this5.$(config.selector).selectedIndex = index;
20343      });
20344    }
20345    /**
20346     * Restore texttrack settings from localStorage
20347     */
20348    ;
20349
20350    _proto.restoreSettings = function restoreSettings() {
20351      var values;
20352
20353      try {
20354        values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
20355      } catch (err) {
20356        log.warn(err);
20357      }
20358
20359      if (values) {
20360        this.setValues(values);
20361      }
20362    }
20363    /**
20364     * Save text track settings to localStorage
20365     */
20366    ;
20367
20368    _proto.saveSettings = function saveSettings() {
20369      if (!this.options_.persistTextTrackSettings) {
20370        return;
20371      }
20372
20373      var values = this.getValues();
20374
20375      try {
20376        if (Object.keys(values).length) {
20377          window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
20378        } else {
20379          window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
20380        }
20381      } catch (err) {
20382        log.warn(err);
20383      }
20384    }
20385    /**
20386     * Update display of text track settings
20387     */
20388    ;
20389
20390    _proto.updateDisplay = function updateDisplay() {
20391      var ttDisplay = this.player_.getChild('textTrackDisplay');
20392
20393      if (ttDisplay) {
20394        ttDisplay.updateDisplay();
20395      }
20396    }
20397    /**
20398     * conditionally blur the element and refocus the captions button
20399     *
20400     * @private
20401     */
20402    ;
20403
20404    _proto.conditionalBlur_ = function conditionalBlur_() {
20405      this.previouslyActiveEl_ = null;
20406      var cb = this.player_.controlBar;
20407      var subsCapsBtn = cb && cb.subsCapsButton;
20408      var ccBtn = cb && cb.captionsButton;
20409
20410      if (subsCapsBtn) {
20411        subsCapsBtn.focus();
20412      } else if (ccBtn) {
20413        ccBtn.focus();
20414      }
20415    };
20416
20417    return TextTrackSettings;
20418  }(ModalDialog);
20419
20420  Component.registerComponent('TextTrackSettings', TextTrackSettings);
20421
20422  /**
20423   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
20424   *
20425   * 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}.
20426   *
20427   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
20428   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
20429   * @example <caption>How to disable the resize manager</caption>
20430   * const player = videojs('#vid', {
20431   *   resizeManager: false
20432   * });
20433   *
20434   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
20435   *
20436   * @extends Component
20437   */
20438
20439  var ResizeManager = /*#__PURE__*/function (_Component) {
20440    inheritsLoose(ResizeManager, _Component);
20441
20442    /**
20443     * Create the ResizeManager.
20444     *
20445     * @param {Object} player
20446     *        The `Player` that this class should be attached to.
20447     *
20448     * @param {Object} [options]
20449     *        The key/value store of ResizeManager options.
20450     *
20451     * @param {Object} [options.ResizeObserver]
20452     *        A polyfill for ResizeObserver can be passed in here.
20453     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
20454     */
20455    function ResizeManager(player, options) {
20456      var _this;
20457
20458      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
20459
20460      if (options.ResizeObserver === null) {
20461        RESIZE_OBSERVER_AVAILABLE = false;
20462      } // Only create an element when ResizeObserver isn't available
20463
20464
20465      var options_ = mergeOptions({
20466        createEl: !RESIZE_OBSERVER_AVAILABLE,
20467        reportTouchActivity: false
20468      }, options);
20469      _this = _Component.call(this, player, options_) || this;
20470      _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
20471      _this.loadListener_ = null;
20472      _this.resizeObserver_ = null;
20473      _this.debouncedHandler_ = debounce(function () {
20474        _this.resizeHandler();
20475      }, 100, false, assertThisInitialized(_this));
20476
20477      if (RESIZE_OBSERVER_AVAILABLE) {
20478        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
20479
20480        _this.resizeObserver_.observe(player.el());
20481      } else {
20482        _this.loadListener_ = function () {
20483          if (!_this.el_ || !_this.el_.contentWindow) {
20484            return;
20485          }
20486
20487          var debouncedHandler_ = _this.debouncedHandler_;
20488
20489          var unloadListener_ = _this.unloadListener_ = function () {
20490            off(this, 'resize', debouncedHandler_);
20491            off(this, 'unload', unloadListener_);
20492            unloadListener_ = null;
20493          }; // safari and edge can unload the iframe before resizemanager dispose
20494          // we have to dispose of event handlers correctly before that happens
20495
20496
20497          on(_this.el_.contentWindow, 'unload', unloadListener_);
20498          on(_this.el_.contentWindow, 'resize', debouncedHandler_);
20499        };
20500
20501        _this.one('load', _this.loadListener_);
20502      }
20503
20504      return _this;
20505    }
20506
20507    var _proto = ResizeManager.prototype;
20508
20509    _proto.createEl = function createEl() {
20510      return _Component.prototype.createEl.call(this, 'iframe', {
20511        className: 'vjs-resize-manager',
20512        tabIndex: -1
20513      }, {
20514        'aria-hidden': 'true'
20515      });
20516    }
20517    /**
20518     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
20519     *
20520     * @fires Player#playerresize
20521     */
20522    ;
20523
20524    _proto.resizeHandler = function resizeHandler() {
20525      /**
20526       * Called when the player size has changed
20527       *
20528       * @event Player#playerresize
20529       * @type {EventTarget~Event}
20530       */
20531      // make sure player is still around to trigger
20532      // prevents this from causing an error after dispose
20533      if (!this.player_ || !this.player_.trigger) {
20534        return;
20535      }
20536
20537      this.player_.trigger('playerresize');
20538    };
20539
20540    _proto.dispose = function dispose() {
20541      if (this.debouncedHandler_) {
20542        this.debouncedHandler_.cancel();
20543      }
20544
20545      if (this.resizeObserver_) {
20546        if (this.player_.el()) {
20547          this.resizeObserver_.unobserve(this.player_.el());
20548        }
20549
20550        this.resizeObserver_.disconnect();
20551      }
20552
20553      if (this.loadListener_) {
20554        this.off('load', this.loadListener_);
20555      }
20556
20557      if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
20558        this.unloadListener_.call(this.el_.contentWindow);
20559      }
20560
20561      this.ResizeObserver = null;
20562      this.resizeObserver = null;
20563      this.debouncedHandler_ = null;
20564      this.loadListener_ = null;
20565
20566      _Component.prototype.dispose.call(this);
20567    };
20568
20569    return ResizeManager;
20570  }(Component);
20571
20572  Component.registerComponent('ResizeManager', ResizeManager);
20573
20574  var defaults = {
20575    trackingThreshold: 30,
20576    liveTolerance: 15
20577  };
20578  /*
20579    track when we are at the live edge, and other helpers for live playback */
20580
20581  /**
20582   * A class for checking live current time and determining when the player
20583   * is at or behind the live edge.
20584   */
20585
20586  var LiveTracker = /*#__PURE__*/function (_Component) {
20587    inheritsLoose(LiveTracker, _Component);
20588
20589    /**
20590     * Creates an instance of this class.
20591     *
20592     * @param {Player} player
20593     *        The `Player` that this class should be attached to.
20594     *
20595     * @param {Object} [options]
20596     *        The key/value store of player options.
20597     *
20598     * @param {number} [options.trackingThreshold=30]
20599     *        Number of seconds of live window (seekableEnd - seekableStart) that
20600     *        media needs to have before the liveui will be shown.
20601     *
20602     * @param {number} [options.liveTolerance=15]
20603     *        Number of seconds behind live that we have to be
20604     *        before we will be considered non-live. Note that this will only
20605     *        be used when playing at the live edge. This allows large seekable end
20606     *        changes to not effect wether we are live or not.
20607     */
20608    function LiveTracker(player, options) {
20609      var _this;
20610
20611      // LiveTracker does not need an element
20612      var options_ = mergeOptions(defaults, options, {
20613        createEl: false
20614      });
20615      _this = _Component.call(this, player, options_) || this;
20616
20617      _this.reset_();
20618
20619      _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
20620      // also, tracking when the document is hidden can
20621      // cause the CPU to spike and eventually crash the page on IE11.
20622
20623
20624      if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
20625        _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
20626      }
20627
20628      return _this;
20629    }
20630    /**
20631     * toggle tracking based on document visiblility
20632     */
20633
20634
20635    var _proto = LiveTracker.prototype;
20636
20637    _proto.handleVisibilityChange = function handleVisibilityChange() {
20638      if (this.player_.duration() !== Infinity) {
20639        return;
20640      }
20641
20642      if (document.hidden) {
20643        this.stopTracking();
20644      } else {
20645        this.startTracking();
20646      }
20647    }
20648    /**
20649     * all the functionality for tracking when seek end changes
20650     * and for tracking how far past seek end we should be
20651     */
20652    ;
20653
20654    _proto.trackLive_ = function trackLive_() {
20655      var seekable = this.player_.seekable(); // skip undefined seekable
20656
20657      if (!seekable || !seekable.length) {
20658        return;
20659      }
20660
20661      var newTime = Number(window$1.performance.now().toFixed(4));
20662      var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
20663      this.lastTime_ = newTime;
20664      this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
20665      var liveCurrentTime = this.liveCurrentTime();
20666      var currentTime = this.player_.currentTime(); // we are behind live if any are true
20667      // 1. the player is paused
20668      // 2. the user seeked to a location 2 seconds away from live
20669      // 3. the difference between live and current time is greater
20670      //    liveTolerance which defaults to 15s
20671
20672      var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if
20673      // 1. until we have not seen a timeupdate yet
20674      // 2. liveCurrentTime is Infinity, which happens on Android
20675
20676      if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
20677        isBehind = false;
20678      }
20679
20680      if (isBehind !== this.behindLiveEdge_) {
20681        this.behindLiveEdge_ = isBehind;
20682        this.trigger('liveedgechange');
20683      }
20684    }
20685    /**
20686     * handle a durationchange event on the player
20687     * and start/stop tracking accordingly.
20688     */
20689    ;
20690
20691    _proto.handleDurationchange = function handleDurationchange() {
20692      if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
20693        if (this.player_.options_.liveui) {
20694          this.player_.addClass('vjs-liveui');
20695        }
20696
20697        this.startTracking();
20698      } else {
20699        this.player_.removeClass('vjs-liveui');
20700        this.stopTracking();
20701      }
20702    }
20703    /**
20704     * start tracking live playback
20705     */
20706    ;
20707
20708    _proto.startTracking = function startTracking() {
20709      if (this.isTracking()) {
20710        return;
20711      } // If we haven't seen a timeupdate, we need to check whether playback
20712      // began before this component started tracking. This can happen commonly
20713      // when using autoplay.
20714
20715
20716      if (!this.timeupdateSeen_) {
20717        this.timeupdateSeen_ = this.player_.hasStarted();
20718      }
20719
20720      this.trackingInterval_ = this.setInterval(this.trackLive_, UPDATE_REFRESH_INTERVAL);
20721      this.trackLive_();
20722      this.on(this.player_, ['play', 'pause'], this.trackLive_);
20723
20724      if (!this.timeupdateSeen_) {
20725        this.one(this.player_, 'play', this.handlePlay);
20726        this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate);
20727      } else {
20728        this.on(this.player_, 'seeked', this.handleSeeked);
20729      }
20730    }
20731    /**
20732     * handle the first timeupdate on the player if it wasn't already playing
20733     * when live tracker started tracking.
20734     */
20735    ;
20736
20737    _proto.handleFirstTimeupdate = function handleFirstTimeupdate() {
20738      this.timeupdateSeen_ = true;
20739      this.on(this.player_, 'seeked', this.handleSeeked);
20740    }
20741    /**
20742     * Keep track of what time a seek starts, and listen for seeked
20743     * to find where a seek ends.
20744     */
20745    ;
20746
20747    _proto.handleSeeked = function handleSeeked() {
20748      var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
20749      this.seekedBehindLive_ = this.skipNextSeeked_ ? false : timeDiff > 2;
20750      this.skipNextSeeked_ = false;
20751      this.trackLive_();
20752    }
20753    /**
20754     * handle the first play on the player, and make sure that we seek
20755     * right to the live edge.
20756     */
20757    ;
20758
20759    _proto.handlePlay = function handlePlay() {
20760      this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
20761    }
20762    /**
20763     * Stop tracking, and set all internal variables to
20764     * their initial value.
20765     */
20766    ;
20767
20768    _proto.reset_ = function reset_() {
20769      this.lastTime_ = -1;
20770      this.pastSeekEnd_ = 0;
20771      this.lastSeekEnd_ = -1;
20772      this.behindLiveEdge_ = true;
20773      this.timeupdateSeen_ = false;
20774      this.seekedBehindLive_ = false;
20775      this.skipNextSeeked_ = false;
20776      this.clearInterval(this.trackingInterval_);
20777      this.trackingInterval_ = null;
20778      this.off(this.player_, ['play', 'pause'], this.trackLive_);
20779      this.off(this.player_, 'seeked', this.handleSeeked);
20780      this.off(this.player_, 'play', this.handlePlay);
20781      this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate);
20782      this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
20783    }
20784    /**
20785     * stop tracking live playback
20786     */
20787    ;
20788
20789    _proto.stopTracking = function stopTracking() {
20790      if (!this.isTracking()) {
20791        return;
20792      }
20793
20794      this.reset_();
20795      this.trigger('liveedgechange');
20796    }
20797    /**
20798     * A helper to get the player seekable end
20799     * so that we don't have to null check everywhere
20800     *
20801     * @return {number}
20802     *         The furthest seekable end or Infinity.
20803     */
20804    ;
20805
20806    _proto.seekableEnd = function seekableEnd() {
20807      var seekable = this.player_.seekable();
20808      var seekableEnds = [];
20809      var i = seekable ? seekable.length : 0;
20810
20811      while (i--) {
20812        seekableEnds.push(seekable.end(i));
20813      } // grab the furthest seekable end after sorting, or if there are none
20814      // default to Infinity
20815
20816
20817      return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
20818    }
20819    /**
20820     * A helper to get the player seekable start
20821     * so that we don't have to null check everywhere
20822     *
20823     * @return {number}
20824     *         The earliest seekable start or 0.
20825     */
20826    ;
20827
20828    _proto.seekableStart = function seekableStart() {
20829      var seekable = this.player_.seekable();
20830      var seekableStarts = [];
20831      var i = seekable ? seekable.length : 0;
20832
20833      while (i--) {
20834        seekableStarts.push(seekable.start(i));
20835      } // grab the first seekable start after sorting, or if there are none
20836      // default to 0
20837
20838
20839      return seekableStarts.length ? seekableStarts.sort()[0] : 0;
20840    }
20841    /**
20842     * Get the live time window aka
20843     * the amount of time between seekable start and
20844     * live current time.
20845     *
20846     * @return {number}
20847     *         The amount of seconds that are seekable in
20848     *         the live video.
20849     */
20850    ;
20851
20852    _proto.liveWindow = function liveWindow() {
20853      var liveCurrentTime = this.liveCurrentTime();
20854
20855      if (liveCurrentTime === Infinity) {
20856        return Infinity;
20857      }
20858
20859      return liveCurrentTime - this.seekableStart();
20860    }
20861    /**
20862     * Determines if the player is live, only checks if this component
20863     * is tracking live playback or not
20864     *
20865     * @return {boolean}
20866     *         Wether liveTracker is tracking
20867     */
20868    ;
20869
20870    _proto.isLive = function isLive() {
20871      return this.isTracking();
20872    }
20873    /**
20874     * Determines if currentTime is at the live edge and won't fall behind
20875     * on each seekableendchange
20876     *
20877     * @return {boolean}
20878     *         Wether playback is at the live edge
20879     */
20880    ;
20881
20882    _proto.atLiveEdge = function atLiveEdge() {
20883      return !this.behindLiveEdge();
20884    }
20885    /**
20886     * get what we expect the live current time to be
20887     *
20888     * @return {number}
20889     *         The expected live current time
20890     */
20891    ;
20892
20893    _proto.liveCurrentTime = function liveCurrentTime() {
20894      return this.pastSeekEnd() + this.seekableEnd();
20895    }
20896    /**
20897     * The number of seconds that have occured after seekable end
20898     * changed. This will be reset to 0 once seekable end changes.
20899     *
20900     * @return {number}
20901     *         Seconds past the current seekable end
20902     */
20903    ;
20904
20905    _proto.pastSeekEnd = function pastSeekEnd() {
20906      var seekableEnd = this.seekableEnd();
20907
20908      if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
20909        this.pastSeekEnd_ = 0;
20910      }
20911
20912      this.lastSeekEnd_ = seekableEnd;
20913      return this.pastSeekEnd_;
20914    }
20915    /**
20916     * If we are currently behind the live edge, aka currentTime will be
20917     * behind on a seekableendchange
20918     *
20919     * @return {boolean}
20920     *         If we are behind the live edge
20921     */
20922    ;
20923
20924    _proto.behindLiveEdge = function behindLiveEdge() {
20925      return this.behindLiveEdge_;
20926    }
20927    /**
20928     * Wether live tracker is currently tracking or not.
20929     */
20930    ;
20931
20932    _proto.isTracking = function isTracking() {
20933      return typeof this.trackingInterval_ === 'number';
20934    }
20935    /**
20936     * Seek to the live edge if we are behind the live edge
20937     */
20938    ;
20939
20940    _proto.seekToLiveEdge = function seekToLiveEdge() {
20941      this.seekedBehindLive_ = false;
20942
20943      if (this.atLiveEdge()) {
20944        return;
20945      } // skipNextSeeked_
20946
20947
20948      this.skipNextSeeked_ = true;
20949      this.player_.currentTime(this.liveCurrentTime());
20950    }
20951    /**
20952     * Dispose of liveTracker
20953     */
20954    ;
20955
20956    _proto.dispose = function dispose() {
20957      this.off(document, 'visibilitychange', this.handleVisibilityChange);
20958      this.stopTracking();
20959
20960      _Component.prototype.dispose.call(this);
20961    };
20962
20963    return LiveTracker;
20964  }(Component);
20965
20966  Component.registerComponent('LiveTracker', LiveTracker);
20967
20968  /**
20969   * This function is used to fire a sourceset when there is something
20970   * similar to `mediaEl.load()` being called. It will try to find the source via
20971   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20972   * with the source that was found or empty string if we cannot know. If it cannot
20973   * find a source then `sourceset` will not be fired.
20974   *
20975   * @param {Html5} tech
20976   *        The tech object that sourceset was setup on
20977   *
20978   * @return {boolean}
20979   *         returns false if the sourceset was not fired and true otherwise.
20980   */
20981
20982  var sourcesetLoad = function sourcesetLoad(tech) {
20983    var el = tech.el(); // if `el.src` is set, that source will be loaded.
20984
20985    if (el.hasAttribute('src')) {
20986      tech.triggerSourceset(el.src);
20987      return true;
20988    }
20989    /**
20990     * Since there isn't a src property on the media element, source elements will be used for
20991     * implementing the source selection algorithm. This happens asynchronously and
20992     * for most cases were there is more than one source we cannot tell what source will
20993     * be loaded, without re-implementing the source selection algorithm. At this time we are not
20994     * going to do that. There are three special cases that we do handle here though:
20995     *
20996     * 1. If there are no sources, do not fire `sourceset`.
20997     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20998     * 3. If there is more than one `<source>` but all of them have the same `src` url.
20999     *    That will be our src.
21000     */
21001
21002
21003    var sources = tech.$$('source');
21004    var srcUrls = [];
21005    var src = ''; // if there are no sources, do not fire sourceset
21006
21007    if (!sources.length) {
21008      return false;
21009    } // only count valid/non-duplicate source elements
21010
21011
21012    for (var i = 0; i < sources.length; i++) {
21013      var url = sources[i].src;
21014
21015      if (url && srcUrls.indexOf(url) === -1) {
21016        srcUrls.push(url);
21017      }
21018    } // there were no valid sources
21019
21020
21021    if (!srcUrls.length) {
21022      return false;
21023    } // there is only one valid source element url
21024    // use that
21025
21026
21027    if (srcUrls.length === 1) {
21028      src = srcUrls[0];
21029    }
21030
21031    tech.triggerSourceset(src);
21032    return true;
21033  };
21034  /**
21035   * our implementation of an `innerHTML` descriptor for browsers
21036   * that do not have one.
21037   */
21038
21039
21040  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
21041    get: function get() {
21042      return this.cloneNode(true).innerHTML;
21043    },
21044    set: function set(v) {
21045      // make a dummy node to use innerHTML on
21046      var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
21047
21048      dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
21049
21050      var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
21051      // to the document fragment
21052
21053      while (dummy.childNodes.length) {
21054        docFrag.appendChild(dummy.childNodes[0]);
21055      } // remove content
21056
21057
21058      this.innerText = ''; // now we add all of that html in one by appending the
21059      // document fragment. This is how innerHTML does it.
21060
21061      window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
21062
21063      return this.innerHTML;
21064    }
21065  });
21066  /**
21067   * Get a property descriptor given a list of priorities and the
21068   * property to get.
21069   */
21070
21071  var getDescriptor = function getDescriptor(priority, prop) {
21072    var descriptor = {};
21073
21074    for (var i = 0; i < priority.length; i++) {
21075      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
21076
21077      if (descriptor && descriptor.set && descriptor.get) {
21078        break;
21079      }
21080    }
21081
21082    descriptor.enumerable = true;
21083    descriptor.configurable = true;
21084    return descriptor;
21085  };
21086
21087  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
21088    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
21089  };
21090  /**
21091   * Patches browser internal functions so that we can tell synchronously
21092   * if a `<source>` was appended to the media element. For some reason this
21093   * causes a `sourceset` if the the media element is ready and has no source.
21094   * This happens when:
21095   * - The page has just loaded and the media element does not have a source.
21096   * - The media element was emptied of all sources, then `load()` was called.
21097   *
21098   * It does this by patching the following functions/properties when they are supported:
21099   *
21100   * - `append()` - can be used to add a `<source>` element to the media element
21101   * - `appendChild()` - can be used to add a `<source>` element to the media element
21102   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
21103   * - `innerHTML` -  can be used to add a `<source>` element to the media element
21104   *
21105   * @param {Html5} tech
21106   *        The tech object that sourceset is being setup on.
21107   */
21108
21109
21110  var firstSourceWatch = function firstSourceWatch(tech) {
21111    var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
21112
21113    if (el.resetSourceWatch_) {
21114      return;
21115    }
21116
21117    var old = {};
21118    var innerDescriptor = getInnerHTMLDescriptor(tech);
21119
21120    var appendWrapper = function appendWrapper(appendFn) {
21121      return function () {
21122        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
21123          args[_key] = arguments[_key];
21124        }
21125
21126        var retval = appendFn.apply(el, args);
21127        sourcesetLoad(tech);
21128        return retval;
21129      };
21130    };
21131
21132    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
21133      if (!el[k]) {
21134        return;
21135      } // store the old function
21136
21137
21138      old[k] = el[k]; // call the old function with a sourceset if a source
21139      // was loaded
21140
21141      el[k] = appendWrapper(old[k]);
21142    });
21143    Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
21144      set: appendWrapper(innerDescriptor.set)
21145    }));
21146
21147    el.resetSourceWatch_ = function () {
21148      el.resetSourceWatch_ = null;
21149      Object.keys(old).forEach(function (k) {
21150        el[k] = old[k];
21151      });
21152      Object.defineProperty(el, 'innerHTML', innerDescriptor);
21153    }; // on the first sourceset, we need to revert our changes
21154
21155
21156    tech.one('sourceset', el.resetSourceWatch_);
21157  };
21158  /**
21159   * our implementation of a `src` descriptor for browsers
21160   * that do not have one.
21161   */
21162
21163
21164  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
21165    get: function get() {
21166      if (this.hasAttribute('src')) {
21167        return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
21168      }
21169
21170      return '';
21171    },
21172    set: function set(v) {
21173      window$1.Element.prototype.setAttribute.call(this, 'src', v);
21174      return v;
21175    }
21176  });
21177
21178  var getSrcDescriptor = function getSrcDescriptor(tech) {
21179    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
21180  };
21181  /**
21182   * setup `sourceset` handling on the `Html5` tech. This function
21183   * patches the following element properties/functions:
21184   *
21185   * - `src` - to determine when `src` is set
21186   * - `setAttribute()` - to determine when `src` is set
21187   * - `load()` - this re-triggers the source selection algorithm, and can
21188   *              cause a sourceset.
21189   *
21190   * If there is no source when we are adding `sourceset` support or during a `load()`
21191   * we also patch the functions listed in `firstSourceWatch`.
21192   *
21193   * @param {Html5} tech
21194   *        The tech to patch
21195   */
21196
21197
21198  var setupSourceset = function setupSourceset(tech) {
21199    if (!tech.featuresSourceset) {
21200      return;
21201    }
21202
21203    var el = tech.el(); // make sure sourceset isn't setup twice.
21204
21205    if (el.resetSourceset_) {
21206      return;
21207    }
21208
21209    var srcDescriptor = getSrcDescriptor(tech);
21210    var oldSetAttribute = el.setAttribute;
21211    var oldLoad = el.load;
21212    Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
21213      set: function set(v) {
21214        var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
21215
21216        tech.triggerSourceset(el.src);
21217        return retval;
21218      }
21219    }));
21220
21221    el.setAttribute = function (n, v) {
21222      var retval = oldSetAttribute.call(el, n, v);
21223
21224      if (/src/i.test(n)) {
21225        tech.triggerSourceset(el.src);
21226      }
21227
21228      return retval;
21229    };
21230
21231    el.load = function () {
21232      var retval = oldLoad.call(el); // if load was called, but there was no source to fire
21233      // sourceset on. We have to watch for a source append
21234      // as that can trigger a `sourceset` when the media element
21235      // has no source
21236
21237      if (!sourcesetLoad(tech)) {
21238        tech.triggerSourceset('');
21239        firstSourceWatch(tech);
21240      }
21241
21242      return retval;
21243    };
21244
21245    if (el.currentSrc) {
21246      tech.triggerSourceset(el.currentSrc);
21247    } else if (!sourcesetLoad(tech)) {
21248      firstSourceWatch(tech);
21249    }
21250
21251    el.resetSourceset_ = function () {
21252      el.resetSourceset_ = null;
21253      el.load = oldLoad;
21254      el.setAttribute = oldSetAttribute;
21255      Object.defineProperty(el, 'src', srcDescriptor);
21256
21257      if (el.resetSourceWatch_) {
21258        el.resetSourceWatch_();
21259      }
21260    };
21261  };
21262
21263  /**
21264   * Object.defineProperty but "lazy", which means that the value is only set after
21265   * it retrieved the first time, rather than being set right away.
21266   *
21267   * @param {Object} obj the object to set the property on
21268   * @param {string} key the key for the property to set
21269   * @param {Function} getValue the function used to get the value when it is needed.
21270   * @param {boolean} setter wether a setter shoould be allowed or not
21271   */
21272  var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) {
21273    if (setter === void 0) {
21274      setter = true;
21275    }
21276
21277    var set = function set(value) {
21278      return Object.defineProperty(obj, key, {
21279        value: value,
21280        enumerable: true,
21281        writable: true
21282      });
21283    };
21284
21285    var options = {
21286      configurable: true,
21287      enumerable: true,
21288      get: function get() {
21289        var value = getValue();
21290        set(value);
21291        return value;
21292      }
21293    };
21294
21295    if (setter) {
21296      options.set = set;
21297    }
21298
21299    return Object.defineProperty(obj, key, options);
21300  };
21301
21302  /**
21303   * HTML5 Media Controller - Wrapper for HTML5 Media API
21304   *
21305   * @mixes Tech~SourceHandlerAdditions
21306   * @extends Tech
21307   */
21308
21309  var Html5 = /*#__PURE__*/function (_Tech) {
21310    inheritsLoose(Html5, _Tech);
21311
21312    /**
21313    * Create an instance of this Tech.
21314    *
21315    * @param {Object} [options]
21316    *        The key/value store of player options.
21317    *
21318    * @param {Component~ReadyCallback} ready
21319    *        Callback function to call when the `HTML5` Tech is ready.
21320    */
21321    function Html5(options, ready) {
21322      var _this;
21323
21324      _this = _Tech.call(this, options, ready) || this;
21325      var source = options.source;
21326      var crossoriginTracks = false; // Set the source if one is provided
21327      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
21328      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
21329      // anyway so the error gets fired.
21330
21331      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
21332        _this.setSource(source);
21333      } else {
21334        _this.handleLateInit_(_this.el_);
21335      } // setup sourceset after late sourceset/init
21336
21337
21338      if (options.enableSourceset) {
21339        _this.setupSourcesetHandling_();
21340      }
21341
21342      if (_this.el_.hasChildNodes()) {
21343        var nodes = _this.el_.childNodes;
21344        var nodesLength = nodes.length;
21345        var removeNodes = [];
21346
21347        while (nodesLength--) {
21348          var node = nodes[nodesLength];
21349          var nodeName = node.nodeName.toLowerCase();
21350
21351          if (nodeName === 'track') {
21352            if (!_this.featuresNativeTextTracks) {
21353              // Empty video tag tracks so the built-in player doesn't use them also.
21354              // This may not be fast enough to stop HTML5 browsers from reading the tags
21355              // so we'll need to turn off any default tracks if we're manually doing
21356              // captions and subtitles. videoElement.textTracks
21357              removeNodes.push(node);
21358            } else {
21359              // store HTMLTrackElement and TextTrack to remote list
21360              _this.remoteTextTrackEls().addTrackElement_(node);
21361
21362              _this.remoteTextTracks().addTrack(node.track);
21363
21364              _this.textTracks().addTrack(node.track);
21365
21366              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
21367                crossoriginTracks = true;
21368              }
21369            }
21370          }
21371        }
21372
21373        for (var i = 0; i < removeNodes.length; i++) {
21374          _this.el_.removeChild(removeNodes[i]);
21375        }
21376      }
21377
21378      _this.proxyNativeTracks_();
21379
21380      if (_this.featuresNativeTextTracks && crossoriginTracks) {
21381        log.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
21382      } // prevent iOS Safari from disabling metadata text tracks during native playback
21383
21384
21385      _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
21386      // Our goal should be to get the custom controls on mobile solid everywhere
21387      // so we can remove this all together. Right now this will block custom
21388      // controls on touch enabled laptops like the Chrome Pixel
21389
21390
21391      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
21392        _this.setControls(true);
21393      } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
21394      // into a `fullscreenchange` event
21395
21396
21397      _this.proxyWebkitFullscreen_();
21398
21399      _this.triggerReady();
21400
21401      return _this;
21402    }
21403    /**
21404     * Dispose of `HTML5` media element and remove all tracks.
21405     */
21406
21407
21408    var _proto = Html5.prototype;
21409
21410    _proto.dispose = function dispose() {
21411      if (this.el_ && this.el_.resetSourceset_) {
21412        this.el_.resetSourceset_();
21413      }
21414
21415      Html5.disposeMediaElement(this.el_);
21416      this.options_ = null; // tech will handle clearing of the emulated track list
21417
21418      _Tech.prototype.dispose.call(this);
21419    }
21420    /**
21421     * Modify the media element so that we can detect when
21422     * the source is changed. Fires `sourceset` just after the source has changed
21423     */
21424    ;
21425
21426    _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
21427      setupSourceset(this);
21428    }
21429    /**
21430     * When a captions track is enabled in the iOS Safari native player, all other
21431     * tracks are disabled (including metadata tracks), which nulls all of their
21432     * associated cue points. This will restore metadata tracks to their pre-fullscreen
21433     * state in those cases so that cue points are not needlessly lost.
21434     *
21435     * @private
21436     */
21437    ;
21438
21439    _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
21440      var textTracks = this.textTracks();
21441      var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
21442
21443      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
21444        metadataTracksPreFullscreenState = [];
21445
21446        for (var i = 0; i < textTracks.length; i++) {
21447          var track = textTracks[i];
21448
21449          if (track.kind === 'metadata') {
21450            metadataTracksPreFullscreenState.push({
21451              track: track,
21452              storedMode: track.mode
21453            });
21454          }
21455        }
21456      }; // snapshot each metadata track's initial state, and update the snapshot
21457      // each time there is a track 'change' event
21458
21459
21460      takeMetadataTrackSnapshot();
21461      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
21462      this.on('dispose', function () {
21463        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
21464      });
21465
21466      var restoreTrackMode = function restoreTrackMode() {
21467        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
21468          var storedTrack = metadataTracksPreFullscreenState[i];
21469
21470          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
21471            storedTrack.track.mode = storedTrack.storedMode;
21472          }
21473        } // we only want this handler to be executed on the first 'change' event
21474
21475
21476        textTracks.removeEventListener('change', restoreTrackMode);
21477      }; // when we enter fullscreen playback, stop updating the snapshot and
21478      // restore all track modes to their pre-fullscreen state
21479
21480
21481      this.on('webkitbeginfullscreen', function () {
21482        textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
21483
21484        textTracks.removeEventListener('change', restoreTrackMode);
21485        textTracks.addEventListener('change', restoreTrackMode);
21486      }); // start updating the snapshot again after leaving fullscreen
21487
21488      this.on('webkitendfullscreen', function () {
21489        // remove the listener before adding it just in case it wasn't previously removed
21490        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
21491        textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
21492
21493        textTracks.removeEventListener('change', restoreTrackMode);
21494      });
21495    }
21496    /**
21497     * Attempt to force override of tracks for the given type
21498     *
21499     * @param {string} type - Track type to override, possible values include 'Audio',
21500     * 'Video', and 'Text'.
21501     * @param {boolean} override - If set to true native audio/video will be overridden,
21502     * otherwise native audio/video will potentially be used.
21503     * @private
21504     */
21505    ;
21506
21507    _proto.overrideNative_ = function overrideNative_(type, override) {
21508      var _this2 = this;
21509
21510      // If there is no behavioral change don't add/remove listeners
21511      if (override !== this["featuresNative" + type + "Tracks"]) {
21512        return;
21513      }
21514
21515      var lowerCaseType = type.toLowerCase();
21516
21517      if (this[lowerCaseType + "TracksListeners_"]) {
21518        Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
21519          var elTracks = _this2.el()[lowerCaseType + "Tracks"];
21520
21521          elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
21522        });
21523      }
21524
21525      this["featuresNative" + type + "Tracks"] = !override;
21526      this[lowerCaseType + "TracksListeners_"] = null;
21527      this.proxyNativeTracksForType_(lowerCaseType);
21528    }
21529    /**
21530     * Attempt to force override of native audio tracks.
21531     *
21532     * @param {boolean} override - If set to true native audio will be overridden,
21533     * otherwise native audio will potentially be used.
21534     */
21535    ;
21536
21537    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
21538      this.overrideNative_('Audio', override);
21539    }
21540    /**
21541     * Attempt to force override of native video tracks.
21542     *
21543     * @param {boolean} override - If set to true native video will be overridden,
21544     * otherwise native video will potentially be used.
21545     */
21546    ;
21547
21548    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
21549      this.overrideNative_('Video', override);
21550    }
21551    /**
21552     * Proxy native track list events for the given type to our track
21553     * lists if the browser we are playing in supports that type of track list.
21554     *
21555     * @param {string} name - Track type; values include 'audio', 'video', and 'text'
21556     * @private
21557     */
21558    ;
21559
21560    _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
21561      var _this3 = this;
21562
21563      var props = NORMAL[name];
21564      var elTracks = this.el()[props.getterName];
21565      var techTracks = this[props.getterName]();
21566
21567      if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
21568        return;
21569      }
21570
21571      var listeners = {
21572        change: function change(e) {
21573          var event = {
21574            type: 'change',
21575            target: techTracks,
21576            currentTarget: techTracks,
21577            srcElement: techTracks
21578          };
21579          techTracks.trigger(event); // if we are a text track change event, we should also notify the
21580          // remote text track list. This can potentially cause a false positive
21581          // if we were to get a change event on a non-remote track and
21582          // we triggered the event on the remote text track list which doesn't
21583          // contain that track. However, best practices mean looping through the
21584          // list of tracks and searching for the appropriate mode value, so,
21585          // this shouldn't pose an issue
21586
21587          if (name === 'text') {
21588            _this3[REMOTE.remoteText.getterName]().trigger(event);
21589          }
21590        },
21591        addtrack: function addtrack(e) {
21592          techTracks.addTrack(e.track);
21593        },
21594        removetrack: function removetrack(e) {
21595          techTracks.removeTrack(e.track);
21596        }
21597      };
21598
21599      var removeOldTracks = function removeOldTracks() {
21600        var removeTracks = [];
21601
21602        for (var i = 0; i < techTracks.length; i++) {
21603          var found = false;
21604
21605          for (var j = 0; j < elTracks.length; j++) {
21606            if (elTracks[j] === techTracks[i]) {
21607              found = true;
21608              break;
21609            }
21610          }
21611
21612          if (!found) {
21613            removeTracks.push(techTracks[i]);
21614          }
21615        }
21616
21617        while (removeTracks.length) {
21618          techTracks.removeTrack(removeTracks.shift());
21619        }
21620      };
21621
21622      this[props.getterName + 'Listeners_'] = listeners;
21623      Object.keys(listeners).forEach(function (eventName) {
21624        var listener = listeners[eventName];
21625        elTracks.addEventListener(eventName, listener);
21626
21627        _this3.on('dispose', function (e) {
21628          return elTracks.removeEventListener(eventName, listener);
21629        });
21630      }); // Remove (native) tracks that are not used anymore
21631
21632      this.on('loadstart', removeOldTracks);
21633      this.on('dispose', function (e) {
21634        return _this3.off('loadstart', removeOldTracks);
21635      });
21636    }
21637    /**
21638     * Proxy all native track list events to our track lists if the browser we are playing
21639     * in supports that type of track list.
21640     *
21641     * @private
21642     */
21643    ;
21644
21645    _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
21646      var _this4 = this;
21647
21648      NORMAL.names.forEach(function (name) {
21649        _this4.proxyNativeTracksForType_(name);
21650      });
21651    }
21652    /**
21653     * Create the `Html5` Tech's DOM element.
21654     *
21655     * @return {Element}
21656     *         The element that gets created.
21657     */
21658    ;
21659
21660    _proto.createEl = function createEl() {
21661      var el = this.options_.tag; // Check if this browser supports moving the element into the box.
21662      // On the iPhone video will break if you move the element,
21663      // So we have to create a brand new element.
21664      // If we ingested the player div, we do not need to move the media element.
21665
21666      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
21667        // If the original tag is still there, clone and remove it.
21668        if (el) {
21669          var clone = el.cloneNode(true);
21670
21671          if (el.parentNode) {
21672            el.parentNode.insertBefore(clone, el);
21673          }
21674
21675          Html5.disposeMediaElement(el);
21676          el = clone;
21677        } else {
21678          el = document.createElement('video'); // determine if native controls should be used
21679
21680          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
21681          var attributes = mergeOptions({}, tagAttributes);
21682
21683          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
21684            delete attributes.controls;
21685          }
21686
21687          setAttributes(el, assign(attributes, {
21688            id: this.options_.techId,
21689            "class": 'vjs-tech'
21690          }));
21691        }
21692
21693        el.playerId = this.options_.playerId;
21694      }
21695
21696      if (typeof this.options_.preload !== 'undefined') {
21697        setAttribute(el, 'preload', this.options_.preload);
21698      }
21699
21700      if (this.options_.disablePictureInPicture !== undefined) {
21701        el.disablePictureInPicture = this.options_.disablePictureInPicture;
21702      } // Update specific tag settings, in case they were overridden
21703      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
21704      // when iOS/Safari or other browsers attempt to autoplay.
21705
21706
21707      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
21708
21709      for (var i = 0; i < settingsAttrs.length; i++) {
21710        var attr = settingsAttrs[i];
21711        var value = this.options_[attr];
21712
21713        if (typeof value !== 'undefined') {
21714          if (value) {
21715            setAttribute(el, attr, attr);
21716          } else {
21717            removeAttribute(el, attr);
21718          }
21719
21720          el[attr] = value;
21721        }
21722      }
21723
21724      return el;
21725    }
21726    /**
21727     * This will be triggered if the loadstart event has already fired, before videojs was
21728     * ready. Two known examples of when this can happen are:
21729     * 1. If we're loading the playback object after it has started loading
21730     * 2. The media is already playing the (often with autoplay on) then
21731     *
21732     * This function will fire another loadstart so that videojs can catchup.
21733     *
21734     * @fires Tech#loadstart
21735     *
21736     * @return {undefined}
21737     *         returns nothing.
21738     */
21739    ;
21740
21741    _proto.handleLateInit_ = function handleLateInit_(el) {
21742      if (el.networkState === 0 || el.networkState === 3) {
21743        // The video element hasn't started loading the source yet
21744        // or didn't find a source
21745        return;
21746      }
21747
21748      if (el.readyState === 0) {
21749        // NetworkState is set synchronously BUT loadstart is fired at the
21750        // end of the current stack, usually before setInterval(fn, 0).
21751        // So at this point we know loadstart may have already fired or is
21752        // about to fire, and either way the player hasn't seen it yet.
21753        // We don't want to fire loadstart prematurely here and cause a
21754        // double loadstart so we'll wait and see if it happens between now
21755        // and the next loop, and fire it if not.
21756        // HOWEVER, we also want to make sure it fires before loadedmetadata
21757        // which could also happen between now and the next loop, so we'll
21758        // watch for that also.
21759        var loadstartFired = false;
21760
21761        var setLoadstartFired = function setLoadstartFired() {
21762          loadstartFired = true;
21763        };
21764
21765        this.on('loadstart', setLoadstartFired);
21766
21767        var triggerLoadstart = function triggerLoadstart() {
21768          // We did miss the original loadstart. Make sure the player
21769          // sees loadstart before loadedmetadata
21770          if (!loadstartFired) {
21771            this.trigger('loadstart');
21772          }
21773        };
21774
21775        this.on('loadedmetadata', triggerLoadstart);
21776        this.ready(function () {
21777          this.off('loadstart', setLoadstartFired);
21778          this.off('loadedmetadata', triggerLoadstart);
21779
21780          if (!loadstartFired) {
21781            // We did miss the original native loadstart. Fire it now.
21782            this.trigger('loadstart');
21783          }
21784        });
21785        return;
21786      } // From here on we know that loadstart already fired and we missed it.
21787      // The other readyState events aren't as much of a problem if we double
21788      // them, so not going to go to as much trouble as loadstart to prevent
21789      // that unless we find reason to.
21790
21791
21792      var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
21793
21794      eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
21795
21796      if (el.readyState >= 2) {
21797        eventsToTrigger.push('loadeddata');
21798      } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
21799
21800
21801      if (el.readyState >= 3) {
21802        eventsToTrigger.push('canplay');
21803      } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
21804
21805
21806      if (el.readyState >= 4) {
21807        eventsToTrigger.push('canplaythrough');
21808      } // We still need to give the player time to add event listeners
21809
21810
21811      this.ready(function () {
21812        eventsToTrigger.forEach(function (type) {
21813          this.trigger(type);
21814        }, this);
21815      });
21816    }
21817    /**
21818     * Set whether we are scrubbing or not.
21819     * This is used to decide whether we should use `fastSeek` or not.
21820     * `fastSeek` is used to provide trick play on Safari browsers.
21821     *
21822     * @param {boolean} isScrubbing
21823     *                  - true for we are currently scrubbing
21824     *                  - false for we are no longer scrubbing
21825     */
21826    ;
21827
21828    _proto.setScrubbing = function setScrubbing(isScrubbing) {
21829      this.isScrubbing_ = isScrubbing;
21830    }
21831    /**
21832     * Set current time for the `HTML5` tech.
21833     *
21834     * @param {number} seconds
21835     *        Set the current time of the media to this.
21836     */
21837    ;
21838
21839    _proto.setCurrentTime = function setCurrentTime(seconds) {
21840      try {
21841        if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
21842          this.el_.fastSeek(seconds);
21843        } else {
21844          this.el_.currentTime = seconds;
21845        }
21846      } catch (e) {
21847        log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
21848      }
21849    }
21850    /**
21851     * Get the current duration of the HTML5 media element.
21852     *
21853     * @return {number}
21854     *         The duration of the media or 0 if there is no duration.
21855     */
21856    ;
21857
21858    _proto.duration = function duration() {
21859      var _this5 = this;
21860
21861      // Android Chrome will report duration as Infinity for VOD HLS until after
21862      // playback has started, which triggers the live display erroneously.
21863      // Return NaN if playback has not started and trigger a durationupdate once
21864      // the duration can be reliably known.
21865      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
21866        // Wait for the first `timeupdate` with currentTime > 0 - there may be
21867        // several with 0
21868        var checkProgress = function checkProgress() {
21869          if (_this5.el_.currentTime > 0) {
21870            // Trigger durationchange for genuinely live video
21871            if (_this5.el_.duration === Infinity) {
21872              _this5.trigger('durationchange');
21873            }
21874
21875            _this5.off('timeupdate', checkProgress);
21876          }
21877        };
21878
21879        this.on('timeupdate', checkProgress);
21880        return NaN;
21881      }
21882
21883      return this.el_.duration || NaN;
21884    }
21885    /**
21886     * Get the current width of the HTML5 media element.
21887     *
21888     * @return {number}
21889     *         The width of the HTML5 media element.
21890     */
21891    ;
21892
21893    _proto.width = function width() {
21894      return this.el_.offsetWidth;
21895    }
21896    /**
21897     * Get the current height of the HTML5 media element.
21898     *
21899     * @return {number}
21900     *         The height of the HTML5 media element.
21901     */
21902    ;
21903
21904    _proto.height = function height() {
21905      return this.el_.offsetHeight;
21906    }
21907    /**
21908     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
21909     * `fullscreenchange` event.
21910     *
21911     * @private
21912     * @fires fullscreenchange
21913     * @listens webkitendfullscreen
21914     * @listens webkitbeginfullscreen
21915     * @listens webkitbeginfullscreen
21916     */
21917    ;
21918
21919    _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
21920      var _this6 = this;
21921
21922      if (!('webkitDisplayingFullscreen' in this.el_)) {
21923        return;
21924      }
21925
21926      var endFn = function endFn() {
21927        this.trigger('fullscreenchange', {
21928          isFullscreen: false
21929        });
21930      };
21931
21932      var beginFn = function beginFn() {
21933        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
21934          this.one('webkitendfullscreen', endFn);
21935          this.trigger('fullscreenchange', {
21936            isFullscreen: true,
21937            // set a flag in case another tech triggers fullscreenchange
21938            nativeIOSFullscreen: true
21939          });
21940        }
21941      };
21942
21943      this.on('webkitbeginfullscreen', beginFn);
21944      this.on('dispose', function () {
21945        _this6.off('webkitbeginfullscreen', beginFn);
21946
21947        _this6.off('webkitendfullscreen', endFn);
21948      });
21949    }
21950    /**
21951     * Check if fullscreen is supported on the current playback device.
21952     *
21953     * @return {boolean}
21954     *         - True if fullscreen is supported.
21955     *         - False if fullscreen is not supported.
21956     */
21957    ;
21958
21959    _proto.supportsFullScreen = function supportsFullScreen() {
21960      if (typeof this.el_.webkitEnterFullScreen === 'function') {
21961        var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
21962
21963        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
21964          return true;
21965        }
21966      }
21967
21968      return false;
21969    }
21970    /**
21971     * Request that the `HTML5` Tech enter fullscreen.
21972     */
21973    ;
21974
21975    _proto.enterFullScreen = function enterFullScreen() {
21976      var video = this.el_;
21977
21978      if (video.paused && video.networkState <= video.HAVE_METADATA) {
21979        // attempt to prime the video element for programmatic access
21980        // this isn't necessary on the desktop but shouldn't hurt
21981        silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen
21982        // can get iOS ~6.1 devices into a play/pause loop
21983
21984        this.setTimeout(function () {
21985          video.pause();
21986
21987          try {
21988            video.webkitEnterFullScreen();
21989          } catch (e) {
21990            this.trigger('fullscreenerror', e);
21991          }
21992        }, 0);
21993      } else {
21994        try {
21995          video.webkitEnterFullScreen();
21996        } catch (e) {
21997          this.trigger('fullscreenerror', e);
21998        }
21999      }
22000    }
22001    /**
22002     * Request that the `HTML5` Tech exit fullscreen.
22003     */
22004    ;
22005
22006    _proto.exitFullScreen = function exitFullScreen() {
22007      if (!this.el_.webkitDisplayingFullscreen) {
22008        this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
22009        return;
22010      }
22011
22012      this.el_.webkitExitFullScreen();
22013    }
22014    /**
22015     * Create a floating video window always on top of other windows so that users may
22016     * continue consuming media while they interact with other content sites, or
22017     * applications on their device.
22018     *
22019     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
22020     *
22021     * @return {Promise}
22022     *         A promise with a Picture-in-Picture window.
22023     */
22024    ;
22025
22026    _proto.requestPictureInPicture = function requestPictureInPicture() {
22027      return this.el_.requestPictureInPicture();
22028    }
22029    /**
22030     * A getter/setter for the `Html5` Tech's source object.
22031     * > Note: Please use {@link Html5#setSource}
22032     *
22033     * @param {Tech~SourceObject} [src]
22034     *        The source object you want to set on the `HTML5` techs element.
22035     *
22036     * @return {Tech~SourceObject|undefined}
22037     *         - The current source object when a source is not passed in.
22038     *         - undefined when setting
22039     *
22040     * @deprecated Since version 5.
22041     */
22042    ;
22043
22044    _proto.src = function src(_src) {
22045      if (_src === undefined) {
22046        return this.el_.src;
22047      } // Setting src through `src` instead of `setSrc` will be deprecated
22048
22049
22050      this.setSrc(_src);
22051    }
22052    /**
22053     * Reset the tech by removing all sources and then calling
22054     * {@link Html5.resetMediaElement}.
22055     */
22056    ;
22057
22058    _proto.reset = function reset() {
22059      Html5.resetMediaElement(this.el_);
22060    }
22061    /**
22062     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
22063     * the HTML5 media element.
22064     *
22065     * @return {Tech~SourceObject}
22066     *         The current source object from the HTML5 tech. With a fallback to the
22067     *         elements source.
22068     */
22069    ;
22070
22071    _proto.currentSrc = function currentSrc() {
22072      if (this.currentSource_) {
22073        return this.currentSource_.src;
22074      }
22075
22076      return this.el_.currentSrc;
22077    }
22078    /**
22079     * Set controls attribute for the HTML5 media Element.
22080     *
22081     * @param {string} val
22082     *        Value to set the controls attribute to
22083     */
22084    ;
22085
22086    _proto.setControls = function setControls(val) {
22087      this.el_.controls = !!val;
22088    }
22089    /**
22090     * Create and returns a remote {@link TextTrack} object.
22091     *
22092     * @param {string} kind
22093     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
22094     *
22095     * @param {string} [label]
22096     *        Label to identify the text track
22097     *
22098     * @param {string} [language]
22099     *        Two letter language abbreviation
22100     *
22101     * @return {TextTrack}
22102     *         The TextTrack that gets created.
22103     */
22104    ;
22105
22106    _proto.addTextTrack = function addTextTrack(kind, label, language) {
22107      if (!this.featuresNativeTextTracks) {
22108        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
22109      }
22110
22111      return this.el_.addTextTrack(kind, label, language);
22112    }
22113    /**
22114     * Creates either native TextTrack or an emulated TextTrack depending
22115     * on the value of `featuresNativeTextTracks`
22116     *
22117     * @param {Object} options
22118     *        The object should contain the options to initialize the TextTrack with.
22119     *
22120     * @param {string} [options.kind]
22121     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
22122     *
22123     * @param {string} [options.label]
22124     *        Label to identify the text track
22125     *
22126     * @param {string} [options.language]
22127     *        Two letter language abbreviation.
22128     *
22129     * @param {boolean} [options.default]
22130     *        Default this track to on.
22131     *
22132     * @param {string} [options.id]
22133     *        The internal id to assign this track.
22134     *
22135     * @param {string} [options.src]
22136     *        A source url for the track.
22137     *
22138     * @return {HTMLTrackElement}
22139     *         The track element that gets created.
22140     */
22141    ;
22142
22143    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
22144      if (!this.featuresNativeTextTracks) {
22145        return _Tech.prototype.createRemoteTextTrack.call(this, options);
22146      }
22147
22148      var htmlTrackElement = document.createElement('track');
22149
22150      if (options.kind) {
22151        htmlTrackElement.kind = options.kind;
22152      }
22153
22154      if (options.label) {
22155        htmlTrackElement.label = options.label;
22156      }
22157
22158      if (options.language || options.srclang) {
22159        htmlTrackElement.srclang = options.language || options.srclang;
22160      }
22161
22162      if (options["default"]) {
22163        htmlTrackElement["default"] = options["default"];
22164      }
22165
22166      if (options.id) {
22167        htmlTrackElement.id = options.id;
22168      }
22169
22170      if (options.src) {
22171        htmlTrackElement.src = options.src;
22172      }
22173
22174      return htmlTrackElement;
22175    }
22176    /**
22177     * Creates a remote text track object and returns an html track element.
22178     *
22179     * @param {Object} options The object should contain values for
22180     * kind, language, label, and src (location of the WebVTT file)
22181     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
22182     * automatically removed from the video element whenever the source changes
22183     * @return {HTMLTrackElement} An Html Track Element.
22184     * This can be an emulated {@link HTMLTrackElement} or a native one.
22185     * @deprecated The default value of the "manualCleanup" parameter will default
22186     * to "false" in upcoming versions of Video.js
22187     */
22188    ;
22189
22190    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
22191      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
22192
22193      if (this.featuresNativeTextTracks) {
22194        this.el().appendChild(htmlTrackElement);
22195      }
22196
22197      return htmlTrackElement;
22198    }
22199    /**
22200     * Remove remote `TextTrack` from `TextTrackList` object
22201     *
22202     * @param {TextTrack} track
22203     *        `TextTrack` object to remove
22204     */
22205    ;
22206
22207    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
22208      _Tech.prototype.removeRemoteTextTrack.call(this, track);
22209
22210      if (this.featuresNativeTextTracks) {
22211        var tracks = this.$$('track');
22212        var i = tracks.length;
22213
22214        while (i--) {
22215          if (track === tracks[i] || track === tracks[i].track) {
22216            this.el().removeChild(tracks[i]);
22217          }
22218        }
22219      }
22220    }
22221    /**
22222     * Gets available media playback quality metrics as specified by the W3C's Media
22223     * Playback Quality API.
22224     *
22225     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
22226     *
22227     * @return {Object}
22228     *         An object with supported media playback quality metrics
22229     */
22230    ;
22231
22232    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
22233      if (typeof this.el().getVideoPlaybackQuality === 'function') {
22234        return this.el().getVideoPlaybackQuality();
22235      }
22236
22237      var videoPlaybackQuality = {};
22238
22239      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
22240        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
22241        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
22242      }
22243
22244      if (window$1.performance && typeof window$1.performance.now === 'function') {
22245        videoPlaybackQuality.creationTime = window$1.performance.now();
22246      } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
22247        videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
22248      }
22249
22250      return videoPlaybackQuality;
22251    };
22252
22253    return Html5;
22254  }(Tech);
22255  /* HTML5 Support Testing ---------------------------------------------------- */
22256
22257  /**
22258   * Element for testing browser HTML5 media capabilities
22259   *
22260   * @type {Element}
22261   * @constant
22262   * @private
22263   */
22264
22265
22266  defineLazyProperty(Html5, 'TEST_VID', function () {
22267    if (!isReal()) {
22268      return;
22269    }
22270
22271    var video = document.createElement('video');
22272    var track = document.createElement('track');
22273    track.kind = 'captions';
22274    track.srclang = 'en';
22275    track.label = 'English';
22276    video.appendChild(track);
22277    return video;
22278  });
22279  /**
22280   * Check if HTML5 media is supported by this browser/device.
22281   *
22282   * @return {boolean}
22283   *         - True if HTML5 media is supported.
22284   *         - False if HTML5 media is not supported.
22285   */
22286
22287  Html5.isSupported = function () {
22288    // IE with no Media Player is a LIAR! (#984)
22289    try {
22290      Html5.TEST_VID.volume = 0.5;
22291    } catch (e) {
22292      return false;
22293    }
22294
22295    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
22296  };
22297  /**
22298   * Check if the tech can support the given type
22299   *
22300   * @param {string} type
22301   *        The mimetype to check
22302   * @return {string} 'probably', 'maybe', or '' (empty string)
22303   */
22304
22305
22306  Html5.canPlayType = function (type) {
22307    return Html5.TEST_VID.canPlayType(type);
22308  };
22309  /**
22310   * Check if the tech can support the given source
22311   *
22312   * @param {Object} srcObj
22313   *        The source object
22314   * @param {Object} options
22315   *        The options passed to the tech
22316   * @return {string} 'probably', 'maybe', or '' (empty string)
22317   */
22318
22319
22320  Html5.canPlaySource = function (srcObj, options) {
22321    return Html5.canPlayType(srcObj.type);
22322  };
22323  /**
22324   * Check if the volume can be changed in this browser/device.
22325   * Volume cannot be changed in a lot of mobile devices.
22326   * Specifically, it can't be changed from 1 on iOS.
22327   *
22328   * @return {boolean}
22329   *         - True if volume can be controlled
22330   *         - False otherwise
22331   */
22332
22333
22334  Html5.canControlVolume = function () {
22335    // IE will error if Windows Media Player not installed #3315
22336    try {
22337      var volume = Html5.TEST_VID.volume;
22338      Html5.TEST_VID.volume = volume / 2 + 0.1;
22339      return volume !== Html5.TEST_VID.volume;
22340    } catch (e) {
22341      return false;
22342    }
22343  };
22344  /**
22345   * Check if the volume can be muted in this browser/device.
22346   * Some devices, e.g. iOS, don't allow changing volume
22347   * but permits muting/unmuting.
22348   *
22349   * @return {bolean}
22350   *      - True if volume can be muted
22351   *      - False otherwise
22352   */
22353
22354
22355  Html5.canMuteVolume = function () {
22356    try {
22357      var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
22358      // work, so we want to set both property and attribute
22359
22360      Html5.TEST_VID.muted = !muted;
22361
22362      if (Html5.TEST_VID.muted) {
22363        setAttribute(Html5.TEST_VID, 'muted', 'muted');
22364      } else {
22365        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
22366      }
22367
22368      return muted !== Html5.TEST_VID.muted;
22369    } catch (e) {
22370      return false;
22371    }
22372  };
22373  /**
22374   * Check if the playback rate can be changed in this browser/device.
22375   *
22376   * @return {boolean}
22377   *         - True if playback rate can be controlled
22378   *         - False otherwise
22379   */
22380
22381
22382  Html5.canControlPlaybackRate = function () {
22383    // Playback rate API is implemented in Android Chrome, but doesn't do anything
22384    // https://github.com/videojs/video.js/issues/3180
22385    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
22386      return false;
22387    } // IE will error if Windows Media Player not installed #3315
22388
22389
22390    try {
22391      var playbackRate = Html5.TEST_VID.playbackRate;
22392      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
22393      return playbackRate !== Html5.TEST_VID.playbackRate;
22394    } catch (e) {
22395      return false;
22396    }
22397  };
22398  /**
22399   * Check if we can override a video/audio elements attributes, with
22400   * Object.defineProperty.
22401   *
22402   * @return {boolean}
22403   *         - True if builtin attributes can be overridden
22404   *         - False otherwise
22405   */
22406
22407
22408  Html5.canOverrideAttributes = function () {
22409    // if we cannot overwrite the src/innerHTML property, there is no support
22410    // iOS 7 safari for instance cannot do this.
22411    try {
22412      var noop = function noop() {};
22413
22414      Object.defineProperty(document.createElement('video'), 'src', {
22415        get: noop,
22416        set: noop
22417      });
22418      Object.defineProperty(document.createElement('audio'), 'src', {
22419        get: noop,
22420        set: noop
22421      });
22422      Object.defineProperty(document.createElement('video'), 'innerHTML', {
22423        get: noop,
22424        set: noop
22425      });
22426      Object.defineProperty(document.createElement('audio'), 'innerHTML', {
22427        get: noop,
22428        set: noop
22429      });
22430    } catch (e) {
22431      return false;
22432    }
22433
22434    return true;
22435  };
22436  /**
22437   * Check to see if native `TextTrack`s are supported by this browser/device.
22438   *
22439   * @return {boolean}
22440   *         - True if native `TextTrack`s are supported.
22441   *         - False otherwise
22442   */
22443
22444
22445  Html5.supportsNativeTextTracks = function () {
22446    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
22447  };
22448  /**
22449   * Check to see if native `VideoTrack`s are supported by this browser/device
22450   *
22451   * @return {boolean}
22452   *        - True if native `VideoTrack`s are supported.
22453   *        - False otherwise
22454   */
22455
22456
22457  Html5.supportsNativeVideoTracks = function () {
22458    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
22459  };
22460  /**
22461   * Check to see if native `AudioTrack`s are supported by this browser/device
22462   *
22463   * @return {boolean}
22464   *        - True if native `AudioTrack`s are supported.
22465   *        - False otherwise
22466   */
22467
22468
22469  Html5.supportsNativeAudioTracks = function () {
22470    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
22471  };
22472  /**
22473   * An array of events available on the Html5 tech.
22474   *
22475   * @private
22476   * @type {Array}
22477   */
22478
22479
22480  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'];
22481  /**
22482   * Boolean indicating whether the `Tech` supports volume control.
22483   *
22484   * @type {boolean}
22485   * @default {@link Html5.canControlVolume}
22486   */
22487
22488  /**
22489   * Boolean indicating whether the `Tech` supports muting volume.
22490   *
22491   * @type {bolean}
22492   * @default {@link Html5.canMuteVolume}
22493   */
22494
22495  /**
22496   * Boolean indicating whether the `Tech` supports changing the speed at which the media
22497   * plays. Examples:
22498   *   - Set player to play 2x (twice) as fast
22499   *   - Set player to play 0.5x (half) as fast
22500   *
22501   * @type {boolean}
22502   * @default {@link Html5.canControlPlaybackRate}
22503   */
22504
22505  /**
22506   * Boolean indicating whether the `Tech` supports the `sourceset` event.
22507   *
22508   * @type {boolean}
22509   * @default
22510   */
22511
22512  /**
22513   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
22514   *
22515   * @type {boolean}
22516   * @default {@link Html5.supportsNativeTextTracks}
22517   */
22518
22519  /**
22520   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
22521   *
22522   * @type {boolean}
22523   * @default {@link Html5.supportsNativeVideoTracks}
22524   */
22525
22526  /**
22527   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
22528   *
22529   * @type {boolean}
22530   * @default {@link Html5.supportsNativeAudioTracks}
22531   */
22532
22533  [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
22534    var key = _ref[0],
22535        fn = _ref[1];
22536    defineLazyProperty(Html5.prototype, key, function () {
22537      return Html5[fn]();
22538    }, true);
22539  });
22540  /**
22541   * Boolean indicating whether the `HTML5` tech currently supports the media element
22542   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
22543   * false there. Everywhere else this should be true.
22544   *
22545   * @type {boolean}
22546   * @default
22547   */
22548
22549  Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
22550  //       Is this true?
22551
22552  /**
22553   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
22554   * when going into fullscreen.
22555   *
22556   * @type {boolean}
22557   * @default
22558   */
22559
22560  Html5.prototype.featuresFullscreenResize = true;
22561  /**
22562   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
22563   * If this is false, manual `progress` events will be triggered instead.
22564   *
22565   * @type {boolean}
22566   * @default
22567   */
22568
22569  Html5.prototype.featuresProgressEvents = true;
22570  /**
22571   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
22572   * If this is false, manual `timeupdate` events will be triggered instead.
22573   *
22574   * @default
22575   */
22576
22577  Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- //
22578
22579  var canPlayType;
22580
22581  Html5.patchCanPlayType = function () {
22582    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
22583    // Firefox and Chrome report correctly
22584    if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
22585      canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
22586
22587      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
22588        var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
22589
22590        if (type && mpegurlRE.test(type)) {
22591          return 'maybe';
22592        }
22593
22594        return canPlayType.call(this, type);
22595      };
22596    }
22597  };
22598
22599  Html5.unpatchCanPlayType = function () {
22600    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
22601
22602    if (canPlayType) {
22603      Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
22604    }
22605
22606    return r;
22607  }; // by default, patch the media element
22608
22609
22610  Html5.patchCanPlayType();
22611
22612  Html5.disposeMediaElement = function (el) {
22613    if (!el) {
22614      return;
22615    }
22616
22617    if (el.parentNode) {
22618      el.parentNode.removeChild(el);
22619    } // remove any child track or source nodes to prevent their loading
22620
22621
22622    while (el.hasChildNodes()) {
22623      el.removeChild(el.firstChild);
22624    } // remove any src reference. not setting `src=''` because that causes a warning
22625    // in firefox
22626
22627
22628    el.removeAttribute('src'); // force the media element to update its loading state by calling load()
22629    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
22630
22631    if (typeof el.load === 'function') {
22632      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
22633      (function () {
22634        try {
22635          el.load();
22636        } catch (e) {// not supported
22637        }
22638      })();
22639    }
22640  };
22641
22642  Html5.resetMediaElement = function (el) {
22643    if (!el) {
22644      return;
22645    }
22646
22647    var sources = el.querySelectorAll('source');
22648    var i = sources.length;
22649
22650    while (i--) {
22651      el.removeChild(sources[i]);
22652    } // remove any src reference.
22653    // not setting `src=''` because that throws an error
22654
22655
22656    el.removeAttribute('src');
22657
22658    if (typeof el.load === 'function') {
22659      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
22660      (function () {
22661        try {
22662          el.load();
22663        } catch (e) {// satisfy linter
22664        }
22665      })();
22666    }
22667  };
22668  /* Native HTML5 element property wrapping ----------------------------------- */
22669  // Wrap native boolean attributes with getters that check both property and attribute
22670  // The list is as followed:
22671  // muted, defaultMuted, autoplay, controls, loop, playsinline
22672
22673
22674  [
22675  /**
22676   * Get the value of `muted` from the media element. `muted` indicates
22677   * that the volume for the media should be set to silent. This does not actually change
22678   * the `volume` attribute.
22679   *
22680   * @method Html5#muted
22681   * @return {boolean}
22682   *         - True if the value of `volume` should be ignored and the audio set to silent.
22683   *         - False if the value of `volume` should be used.
22684   *
22685   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
22686   */
22687  'muted',
22688  /**
22689   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
22690   * whether the media should start muted or not. Only changes the default state of the
22691   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
22692   * current state.
22693   *
22694   * @method Html5#defaultMuted
22695   * @return {boolean}
22696   *         - The value of `defaultMuted` from the media element.
22697   *         - True indicates that the media should start muted.
22698   *         - False indicates that the media should not start muted
22699   *
22700   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
22701   */
22702  'defaultMuted',
22703  /**
22704   * Get the value of `autoplay` from the media element. `autoplay` indicates
22705   * that the media should start to play as soon as the page is ready.
22706   *
22707   * @method Html5#autoplay
22708   * @return {boolean}
22709   *         - The value of `autoplay` from the media element.
22710   *         - True indicates that the media should start as soon as the page loads.
22711   *         - False indicates that the media should not start as soon as the page loads.
22712   *
22713   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
22714   */
22715  'autoplay',
22716  /**
22717   * Get the value of `controls` from the media element. `controls` indicates
22718   * whether the native media controls should be shown or hidden.
22719   *
22720   * @method Html5#controls
22721   * @return {boolean}
22722   *         - The value of `controls` from the media element.
22723   *         - True indicates that native controls should be showing.
22724   *         - False indicates that native controls should be hidden.
22725   *
22726   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
22727   */
22728  'controls',
22729  /**
22730   * Get the value of `loop` from the media element. `loop` indicates
22731   * that the media should return to the start of the media and continue playing once
22732   * it reaches the end.
22733   *
22734   * @method Html5#loop
22735   * @return {boolean}
22736   *         - The value of `loop` from the media element.
22737   *         - True indicates that playback should seek back to start once
22738   *           the end of a media is reached.
22739   *         - False indicates that playback should not loop back to the start when the
22740   *           end of the media is reached.
22741   *
22742   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
22743   */
22744  'loop',
22745  /**
22746   * Get the value of `playsinline` from the media element. `playsinline` indicates
22747   * to the browser that non-fullscreen playback is preferred when fullscreen
22748   * playback is the native default, such as in iOS Safari.
22749   *
22750   * @method Html5#playsinline
22751   * @return {boolean}
22752   *         - The value of `playsinline` from the media element.
22753   *         - True indicates that the media should play inline.
22754   *         - False indicates that the media should not play inline.
22755   *
22756   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
22757   */
22758  'playsinline'].forEach(function (prop) {
22759    Html5.prototype[prop] = function () {
22760      return this.el_[prop] || this.el_.hasAttribute(prop);
22761    };
22762  }); // Wrap native boolean attributes with setters that set both property and attribute
22763  // The list is as followed:
22764  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
22765  // setControls is special-cased above
22766
22767  [
22768  /**
22769   * Set the value of `muted` on the media element. `muted` indicates that the current
22770   * audio level should be silent.
22771   *
22772   * @method Html5#setMuted
22773   * @param {boolean} muted
22774   *        - True if the audio should be set to silent
22775   *        - False otherwise
22776   *
22777   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
22778   */
22779  'muted',
22780  /**
22781   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
22782   * audio level should be silent, but will only effect the muted level on initial playback..
22783   *
22784   * @method Html5.prototype.setDefaultMuted
22785   * @param {boolean} defaultMuted
22786   *        - True if the audio should be set to silent
22787   *        - False otherwise
22788   *
22789   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
22790   */
22791  'defaultMuted',
22792  /**
22793   * Set the value of `autoplay` on the media element. `autoplay` indicates
22794   * that the media should start to play as soon as the page is ready.
22795   *
22796   * @method Html5#setAutoplay
22797   * @param {boolean} autoplay
22798   *         - True indicates that the media should start as soon as the page loads.
22799   *         - False indicates that the media should not start as soon as the page loads.
22800   *
22801   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
22802   */
22803  'autoplay',
22804  /**
22805   * Set the value of `loop` on the media element. `loop` indicates
22806   * that the media should return to the start of the media and continue playing once
22807   * it reaches the end.
22808   *
22809   * @method Html5#setLoop
22810   * @param {boolean} loop
22811   *         - True indicates that playback should seek back to start once
22812   *           the end of a media is reached.
22813   *         - False indicates that playback should not loop back to the start when the
22814   *           end of the media is reached.
22815   *
22816   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
22817   */
22818  'loop',
22819  /**
22820   * Set the value of `playsinline` from the media element. `playsinline` indicates
22821   * to the browser that non-fullscreen playback is preferred when fullscreen
22822   * playback is the native default, such as in iOS Safari.
22823   *
22824   * @method Html5#setPlaysinline
22825   * @param {boolean} playsinline
22826   *         - True indicates that the media should play inline.
22827   *         - False indicates that the media should not play inline.
22828   *
22829   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
22830   */
22831  'playsinline'].forEach(function (prop) {
22832    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
22833      this.el_[prop] = v;
22834
22835      if (v) {
22836        this.el_.setAttribute(prop, prop);
22837      } else {
22838        this.el_.removeAttribute(prop);
22839      }
22840    };
22841  }); // Wrap native properties with a getter
22842  // The list is as followed
22843  // paused, currentTime, buffered, volume, poster, preload, error, seeking
22844  // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
22845  // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
22846
22847  [
22848  /**
22849   * Get the value of `paused` from the media element. `paused` indicates whether the media element
22850   * is currently paused or not.
22851   *
22852   * @method Html5#paused
22853   * @return {boolean}
22854   *         The value of `paused` from the media element.
22855   *
22856   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
22857   */
22858  'paused',
22859  /**
22860   * Get the value of `currentTime` from the media element. `currentTime` indicates
22861   * the current second that the media is at in playback.
22862   *
22863   * @method Html5#currentTime
22864   * @return {number}
22865   *         The value of `currentTime` from the media element.
22866   *
22867   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
22868   */
22869  'currentTime',
22870  /**
22871   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
22872   * object that represents the parts of the media that are already downloaded and
22873   * available for playback.
22874   *
22875   * @method Html5#buffered
22876   * @return {TimeRange}
22877   *         The value of `buffered` from the media element.
22878   *
22879   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
22880   */
22881  'buffered',
22882  /**
22883   * Get the value of `volume` from the media element. `volume` indicates
22884   * the current playback volume of audio for a media. `volume` will be a value from 0
22885   * (silent) to 1 (loudest and default).
22886   *
22887   * @method Html5#volume
22888   * @return {number}
22889   *         The value of `volume` from the media element. Value will be between 0-1.
22890   *
22891   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22892   */
22893  'volume',
22894  /**
22895   * Get the value of `poster` from the media element. `poster` indicates
22896   * that the url of an image file that can/will be shown when no media data is available.
22897   *
22898   * @method Html5#poster
22899   * @return {string}
22900   *         The value of `poster` from the media element. Value will be a url to an
22901   *         image.
22902   *
22903   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
22904   */
22905  'poster',
22906  /**
22907   * Get the value of `preload` from the media element. `preload` indicates
22908   * what should download before the media is interacted with. It can have the following
22909   * values:
22910   * - none: nothing should be downloaded
22911   * - metadata: poster and the first few frames of the media may be downloaded to get
22912   *   media dimensions and other metadata
22913   * - auto: allow the media and metadata for the media to be downloaded before
22914   *    interaction
22915   *
22916   * @method Html5#preload
22917   * @return {string}
22918   *         The value of `preload` from the media element. Will be 'none', 'metadata',
22919   *         or 'auto'.
22920   *
22921   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22922   */
22923  'preload',
22924  /**
22925   * Get the value of the `error` from the media element. `error` indicates any
22926   * MediaError that may have occurred during playback. If error returns null there is no
22927   * current error.
22928   *
22929   * @method Html5#error
22930   * @return {MediaError|null}
22931   *         The value of `error` from the media element. Will be `MediaError` if there
22932   *         is a current error and null otherwise.
22933   *
22934   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
22935   */
22936  'error',
22937  /**
22938   * Get the value of `seeking` from the media element. `seeking` indicates whether the
22939   * media is currently seeking to a new position or not.
22940   *
22941   * @method Html5#seeking
22942   * @return {boolean}
22943   *         - The value of `seeking` from the media element.
22944   *         - True indicates that the media is currently seeking to a new position.
22945   *         - False indicates that the media is not seeking to a new position at this time.
22946   *
22947   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
22948   */
22949  'seeking',
22950  /**
22951   * Get the value of `seekable` from the media element. `seekable` returns a
22952   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
22953   *
22954   * @method Html5#seekable
22955   * @return {TimeRange}
22956   *         The value of `seekable` from the media element. A `TimeRange` object
22957   *         indicating the current ranges of time that can be seeked to.
22958   *
22959   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
22960   */
22961  'seekable',
22962  /**
22963   * Get the value of `ended` from the media element. `ended` indicates whether
22964   * the media has reached the end or not.
22965   *
22966   * @method Html5#ended
22967   * @return {boolean}
22968   *         - The value of `ended` from the media element.
22969   *         - True indicates that the media has ended.
22970   *         - False indicates that the media has not ended.
22971   *
22972   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
22973   */
22974  'ended',
22975  /**
22976   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22977   * the rate at which the media is currently playing back. Examples:
22978   *   - if playbackRate is set to 2, media will play twice as fast.
22979   *   - if playbackRate is set to 0.5, media will play half as fast.
22980   *
22981   * @method Html5#playbackRate
22982   * @return {number}
22983   *         The value of `playbackRate` from the media element. A number indicating
22984   *         the current playback speed of the media, where 1 is normal speed.
22985   *
22986   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22987   */
22988  'playbackRate',
22989  /**
22990   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
22991   * the rate at which the media is currently playing back. This value will not indicate the current
22992   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
22993   *
22994   * Examples:
22995   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
22996   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
22997   *
22998   * @method Html5.prototype.defaultPlaybackRate
22999   * @return {number}
23000   *         The value of `defaultPlaybackRate` from the media element. A number indicating
23001   *         the current playback speed of the media, where 1 is normal speed.
23002   *
23003   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
23004   */
23005  'defaultPlaybackRate',
23006  /**
23007   * Get the value of 'disablePictureInPicture' from the video element.
23008   *
23009   * @method Html5#disablePictureInPicture
23010   * @return {boolean} value
23011   *         - The value of `disablePictureInPicture` from the video element.
23012   *         - True indicates that the video can't be played in Picture-In-Picture mode
23013   *         - False indicates that the video can be played in Picture-In-Picture mode
23014   *
23015   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
23016   */
23017  'disablePictureInPicture',
23018  /**
23019   * Get the value of `played` from the media element. `played` returns a `TimeRange`
23020   * object representing points in the media timeline that have been played.
23021   *
23022   * @method Html5#played
23023   * @return {TimeRange}
23024   *         The value of `played` from the media element. A `TimeRange` object indicating
23025   *         the ranges of time that have been played.
23026   *
23027   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
23028   */
23029  'played',
23030  /**
23031   * Get the value of `networkState` from the media element. `networkState` indicates
23032   * the current network state. It returns an enumeration from the following list:
23033   * - 0: NETWORK_EMPTY
23034   * - 1: NETWORK_IDLE
23035   * - 2: NETWORK_LOADING
23036   * - 3: NETWORK_NO_SOURCE
23037   *
23038   * @method Html5#networkState
23039   * @return {number}
23040   *         The value of `networkState` from the media element. This will be a number
23041   *         from the list in the description.
23042   *
23043   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
23044   */
23045  'networkState',
23046  /**
23047   * Get the value of `readyState` from the media element. `readyState` indicates
23048   * the current state of the media element. It returns an enumeration from the
23049   * following list:
23050   * - 0: HAVE_NOTHING
23051   * - 1: HAVE_METADATA
23052   * - 2: HAVE_CURRENT_DATA
23053   * - 3: HAVE_FUTURE_DATA
23054   * - 4: HAVE_ENOUGH_DATA
23055   *
23056   * @method Html5#readyState
23057   * @return {number}
23058   *         The value of `readyState` from the media element. This will be a number
23059   *         from the list in the description.
23060   *
23061   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
23062   */
23063  'readyState',
23064  /**
23065   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
23066   * the current width of the video in css pixels.
23067   *
23068   * @method Html5#videoWidth
23069   * @return {number}
23070   *         The value of `videoWidth` from the video element. This will be a number
23071   *         in css pixels.
23072   *
23073   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
23074   */
23075  'videoWidth',
23076  /**
23077   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
23078   * the current height of the video in css pixels.
23079   *
23080   * @method Html5#videoHeight
23081   * @return {number}
23082   *         The value of `videoHeight` from the video element. This will be a number
23083   *         in css pixels.
23084   *
23085   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
23086   */
23087  'videoHeight',
23088  /**
23089   * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
23090   * to the browser that should sent the cookies along with the requests for the
23091   * different assets/playlists
23092   *
23093   * @method Html5#crossOrigin
23094   * @return {string}
23095   *         - anonymous indicates that the media should not sent cookies.
23096   *         - use-credentials indicates that the media should sent cookies along the requests.
23097   *
23098   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
23099   */
23100  'crossOrigin'].forEach(function (prop) {
23101    Html5.prototype[prop] = function () {
23102      return this.el_[prop];
23103    };
23104  }); // Wrap native properties with a setter in this format:
23105  // set + toTitleCase(name)
23106  // The list is as follows:
23107  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
23108  // setDisablePictureInPicture, setCrossOrigin
23109
23110  [
23111  /**
23112   * Set the value of `volume` on the media element. `volume` indicates the current
23113   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
23114   * so on.
23115   *
23116   * @method Html5#setVolume
23117   * @param {number} percentAsDecimal
23118   *        The volume percent as a decimal. Valid range is from 0-1.
23119   *
23120   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
23121   */
23122  'volume',
23123  /**
23124   * Set the value of `src` on the media element. `src` indicates the current
23125   * {@link Tech~SourceObject} for the media.
23126   *
23127   * @method Html5#setSrc
23128   * @param {Tech~SourceObject} src
23129   *        The source object to set as the current source.
23130   *
23131   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
23132   */
23133  'src',
23134  /**
23135   * Set the value of `poster` on the media element. `poster` is the url to
23136   * an image file that can/will be shown when no media data is available.
23137   *
23138   * @method Html5#setPoster
23139   * @param {string} poster
23140   *        The url to an image that should be used as the `poster` for the media
23141   *        element.
23142   *
23143   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
23144   */
23145  'poster',
23146  /**
23147   * Set the value of `preload` on the media element. `preload` indicates
23148   * what should download before the media is interacted with. It can have the following
23149   * values:
23150   * - none: nothing should be downloaded
23151   * - metadata: poster and the first few frames of the media may be downloaded to get
23152   *   media dimensions and other metadata
23153   * - auto: allow the media and metadata for the media to be downloaded before
23154   *    interaction
23155   *
23156   * @method Html5#setPreload
23157   * @param {string} preload
23158   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
23159   *         or 'auto'.
23160   *
23161   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
23162   */
23163  'preload',
23164  /**
23165   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
23166   * the rate at which the media should play back. Examples:
23167   *   - if playbackRate is set to 2, media will play twice as fast.
23168   *   - if playbackRate is set to 0.5, media will play half as fast.
23169   *
23170   * @method Html5#setPlaybackRate
23171   * @return {number}
23172   *         The value of `playbackRate` from the media element. A number indicating
23173   *         the current playback speed of the media, where 1 is normal speed.
23174   *
23175   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
23176   */
23177  'playbackRate',
23178  /**
23179   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
23180   * the rate at which the media should play back upon initial startup. Changing this value
23181   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
23182   *
23183   * Example Values:
23184   *   - if playbackRate is set to 2, media will play twice as fast.
23185   *   - if playbackRate is set to 0.5, media will play half as fast.
23186   *
23187   * @method Html5.prototype.setDefaultPlaybackRate
23188   * @return {number}
23189   *         The value of `defaultPlaybackRate` from the media element. A number indicating
23190   *         the current playback speed of the media, where 1 is normal speed.
23191   *
23192   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
23193   */
23194  'defaultPlaybackRate',
23195  /**
23196   * Prevents the browser from suggesting a Picture-in-Picture context menu
23197   * or to request Picture-in-Picture automatically in some cases.
23198   *
23199   * @method Html5#setDisablePictureInPicture
23200   * @param {boolean} value
23201   *         The true value will disable Picture-in-Picture mode.
23202   *
23203   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
23204   */
23205  'disablePictureInPicture',
23206  /**
23207   * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
23208   * to the browser that should sent the cookies along with the requests for the
23209   * different assets/playlists
23210   *
23211   * @method Html5#setCrossOrigin
23212   * @param {string} crossOrigin
23213   *         - anonymous indicates that the media should not sent cookies.
23214   *         - use-credentials indicates that the media should sent cookies along the requests.
23215   *
23216   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
23217   */
23218  'crossOrigin'].forEach(function (prop) {
23219    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
23220      this.el_[prop] = v;
23221    };
23222  }); // wrap native functions with a function
23223  // The list is as follows:
23224  // pause, load, play
23225
23226  [
23227  /**
23228   * A wrapper around the media elements `pause` function. This will call the `HTML5`
23229   * media elements `pause` function.
23230   *
23231   * @method Html5#pause
23232   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
23233   */
23234  'pause',
23235  /**
23236   * A wrapper around the media elements `load` function. This will call the `HTML5`s
23237   * media element `load` function.
23238   *
23239   * @method Html5#load
23240   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
23241   */
23242  'load',
23243  /**
23244   * A wrapper around the media elements `play` function. This will call the `HTML5`s
23245   * media element `play` function.
23246   *
23247   * @method Html5#play
23248   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
23249   */
23250  'play'].forEach(function (prop) {
23251    Html5.prototype[prop] = function () {
23252      return this.el_[prop]();
23253    };
23254  });
23255  Tech.withSourceHandlers(Html5);
23256  /**
23257   * Native source handler for Html5, simply passes the source to the media element.
23258   *
23259   * @property {Tech~SourceObject} source
23260   *        The source object
23261   *
23262   * @property {Html5} tech
23263   *        The instance of the HTML5 tech.
23264   */
23265
23266  Html5.nativeSourceHandler = {};
23267  /**
23268   * Check if the media element can play the given mime type.
23269   *
23270   * @param {string} type
23271   *        The mimetype to check
23272   *
23273   * @return {string}
23274   *         'probably', 'maybe', or '' (empty string)
23275   */
23276
23277  Html5.nativeSourceHandler.canPlayType = function (type) {
23278    // IE without MediaPlayer throws an error (#519)
23279    try {
23280      return Html5.TEST_VID.canPlayType(type);
23281    } catch (e) {
23282      return '';
23283    }
23284  };
23285  /**
23286   * Check if the media element can handle a source natively.
23287   *
23288   * @param {Tech~SourceObject} source
23289   *         The source object
23290   *
23291   * @param {Object} [options]
23292   *         Options to be passed to the tech.
23293   *
23294   * @return {string}
23295   *         'probably', 'maybe', or '' (empty string).
23296   */
23297
23298
23299  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
23300    // If a type was provided we should rely on that
23301    if (source.type) {
23302      return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
23303    } else if (source.src) {
23304      var ext = getFileExtension(source.src);
23305      return Html5.nativeSourceHandler.canPlayType("video/" + ext);
23306    }
23307
23308    return '';
23309  };
23310  /**
23311   * Pass the source to the native media element.
23312   *
23313   * @param {Tech~SourceObject} source
23314   *        The source object
23315   *
23316   * @param {Html5} tech
23317   *        The instance of the Html5 tech
23318   *
23319   * @param {Object} [options]
23320   *        The options to pass to the source
23321   */
23322
23323
23324  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
23325    tech.setSrc(source.src);
23326  };
23327  /**
23328   * A noop for the native dispose function, as cleanup is not needed.
23329   */
23330
23331
23332  Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
23333
23334
23335  Html5.registerSourceHandler(Html5.nativeSourceHandler);
23336  Tech.registerTech('Html5', Html5);
23337
23338  // on the player when they happen
23339
23340  var TECH_EVENTS_RETRIGGER = [
23341  /**
23342   * Fired while the user agent is downloading media data.
23343   *
23344   * @event Player#progress
23345   * @type {EventTarget~Event}
23346   */
23347
23348  /**
23349   * Retrigger the `progress` event that was triggered by the {@link Tech}.
23350   *
23351   * @private
23352   * @method Player#handleTechProgress_
23353   * @fires Player#progress
23354   * @listens Tech#progress
23355   */
23356  'progress',
23357  /**
23358   * Fires when the loading of an audio/video is aborted.
23359   *
23360   * @event Player#abort
23361   * @type {EventTarget~Event}
23362   */
23363
23364  /**
23365   * Retrigger the `abort` event that was triggered by the {@link Tech}.
23366   *
23367   * @private
23368   * @method Player#handleTechAbort_
23369   * @fires Player#abort
23370   * @listens Tech#abort
23371   */
23372  'abort',
23373  /**
23374   * Fires when the browser is intentionally not getting media data.
23375   *
23376   * @event Player#suspend
23377   * @type {EventTarget~Event}
23378   */
23379
23380  /**
23381   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
23382   *
23383   * @private
23384   * @method Player#handleTechSuspend_
23385   * @fires Player#suspend
23386   * @listens Tech#suspend
23387   */
23388  'suspend',
23389  /**
23390   * Fires when the current playlist is empty.
23391   *
23392   * @event Player#emptied
23393   * @type {EventTarget~Event}
23394   */
23395
23396  /**
23397   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
23398   *
23399   * @private
23400   * @method Player#handleTechEmptied_
23401   * @fires Player#emptied
23402   * @listens Tech#emptied
23403   */
23404  'emptied',
23405  /**
23406   * Fires when the browser is trying to get media data, but data is not available.
23407   *
23408   * @event Player#stalled
23409   * @type {EventTarget~Event}
23410   */
23411
23412  /**
23413   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
23414   *
23415   * @private
23416   * @method Player#handleTechStalled_
23417   * @fires Player#stalled
23418   * @listens Tech#stalled
23419   */
23420  'stalled',
23421  /**
23422   * Fires when the browser has loaded meta data for the audio/video.
23423   *
23424   * @event Player#loadedmetadata
23425   * @type {EventTarget~Event}
23426   */
23427
23428  /**
23429   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
23430   *
23431   * @private
23432   * @method Player#handleTechLoadedmetadata_
23433   * @fires Player#loadedmetadata
23434   * @listens Tech#loadedmetadata
23435   */
23436  'loadedmetadata',
23437  /**
23438   * Fires when the browser has loaded the current frame of the audio/video.
23439   *
23440   * @event Player#loadeddata
23441   * @type {event}
23442   */
23443
23444  /**
23445   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
23446   *
23447   * @private
23448   * @method Player#handleTechLoaddeddata_
23449   * @fires Player#loadeddata
23450   * @listens Tech#loadeddata
23451   */
23452  'loadeddata',
23453  /**
23454   * Fires when the current playback position has changed.
23455   *
23456   * @event Player#timeupdate
23457   * @type {event}
23458   */
23459
23460  /**
23461   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
23462   *
23463   * @private
23464   * @method Player#handleTechTimeUpdate_
23465   * @fires Player#timeupdate
23466   * @listens Tech#timeupdate
23467   */
23468  'timeupdate',
23469  /**
23470   * Fires when the video's intrinsic dimensions change
23471   *
23472   * @event Player#resize
23473   * @type {event}
23474   */
23475
23476  /**
23477   * Retrigger the `resize` event that was triggered by the {@link Tech}.
23478   *
23479   * @private
23480   * @method Player#handleTechResize_
23481   * @fires Player#resize
23482   * @listens Tech#resize
23483   */
23484  'resize',
23485  /**
23486   * Fires when the volume has been changed
23487   *
23488   * @event Player#volumechange
23489   * @type {event}
23490   */
23491
23492  /**
23493   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
23494   *
23495   * @private
23496   * @method Player#handleTechVolumechange_
23497   * @fires Player#volumechange
23498   * @listens Tech#volumechange
23499   */
23500  'volumechange',
23501  /**
23502   * Fires when the text track has been changed
23503   *
23504   * @event Player#texttrackchange
23505   * @type {event}
23506   */
23507
23508  /**
23509   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
23510   *
23511   * @private
23512   * @method Player#handleTechTexttrackchange_
23513   * @fires Player#texttrackchange
23514   * @listens Tech#texttrackchange
23515   */
23516  'texttrackchange']; // events to queue when playback rate is zero
23517  // this is a hash for the sole purpose of mapping non-camel-cased event names
23518  // to camel-cased function names
23519
23520  var TECH_EVENTS_QUEUE = {
23521    canplay: 'CanPlay',
23522    canplaythrough: 'CanPlayThrough',
23523    playing: 'Playing',
23524    seeked: 'Seeked'
23525  };
23526  var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
23527  var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
23528  // grep: vjs-layout-x-small
23529  // grep: vjs-layout-small
23530  // grep: vjs-layout-medium
23531  // grep: vjs-layout-large
23532  // grep: vjs-layout-x-large
23533  // grep: vjs-layout-huge
23534
vendor: 24,276 bytes, lines 23535-24265
23535  BREAKPOINT_ORDER.forEach(function (k) {
23536    var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
23537    BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
23538  });
23539  var DEFAULT_BREAKPOINTS = {
23540    tiny: 210,
23541    xsmall: 320,
23542    small: 425,
23543    medium: 768,
23544    large: 1440,
23545    xlarge: 2560,
23546    huge: Infinity
23547  };
23548  /**
23549   * An instance of the `Player` class is created when any of the Video.js setup methods
23550   * are used to initialize a video.
23551   *
23552   * After an instance has been created it can be accessed globally in two ways:
23553   * 1. By calling `videojs('example_video_1');`
23554   * 2. By using it directly via  `videojs.players.example_video_1;`
23555   *
23556   * @extends Component
23557   */
23558
23559  var Player = /*#__PURE__*/function (_Component) {
23560    inheritsLoose(Player, _Component);
23561
23562    /**
23563     * Create an instance of this class.
23564     *
23565     * @param {Element} tag
23566     *        The original video DOM element used for configuring options.
23567     *
23568     * @param {Object} [options]
23569     *        Object of option names and values.
23570     *
23571     * @param {Component~ReadyCallback} [ready]
23572     *        Ready callback function.
23573     */
23574    function Player(tag, options, ready) {
23575      var _this;
23576
23577      // Make sure tag ID exists
23578      tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
23579      // The options argument overrides options set in the video tag
23580      // which overrides globally set options.
23581      // This latter part coincides with the load order
23582      // (tag must exist before Player)
23583
23584      options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
23585      // player properties first, and can't use `this` before `super()`
23586
23587      options.initChildren = false; // Same with creating the element
23588
23589      options.createEl = false; // don't auto mixin the evented mixin
23590
23591      options.evented = false; // we don't want the player to report touch activity on itself
23592      // see enableTouchActivity in Component
23593
23594      options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
23595
23596      if (!options.language) {
23597        if (typeof tag.closest === 'function') {
23598          var closest = tag.closest('[lang]');
23599
23600          if (closest && closest.getAttribute) {
23601            options.language = closest.getAttribute('lang');
23602          }
23603        } else {
23604          var element = tag;
23605
23606          while (element && element.nodeType === 1) {
23607            if (getAttributes(element).hasOwnProperty('lang')) {
23608              options.language = element.getAttribute('lang');
23609              break;
23610            }
23611
23612            element = element.parentNode;
23613          }
23614        }
23615      } // Run base component initializing with new options
23616
23617
23618      _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
23619
23620      _this.boundDocumentFullscreenChange_ = bind(assertThisInitialized(_this), _this.documentFullscreenChange_);
23621      _this.boundFullWindowOnEscKey_ = bind(assertThisInitialized(_this), _this.fullWindowOnEscKey); // default isFullscreen_ to false
23622
23623      _this.isFullscreen_ = false; // create logger
23624
23625      _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests
23626
23627      _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster
23628
23629      _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
23630      // and a seek is happening
23631
23632      _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
23633
23634      _this.isReady_ = false; // Init state hasStarted_
23635
23636      _this.hasStarted_ = false; // Init state userActive_
23637
23638      _this.userActive_ = false; // Init debugEnabled_
23639
23640      _this.debugEnabled_ = false; // if the global option object was accidentally blown away by
23641      // someone, bail early with an informative error
23642
23643      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
23644        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
23645      } // Store the original tag used to set options
23646
23647
23648      _this.tag = tag; // Store the tag attributes used to restore html5 element
23649
23650      _this.tagAttributes = tag && getAttributes(tag); // Update current language
23651
23652      _this.language(_this.options_.language); // Update Supported Languages
23653
23654
23655      if (options.languages) {
23656        // Normalise player option languages to lowercase
23657        var languagesToLower = {};
23658        Object.getOwnPropertyNames(options.languages).forEach(function (name) {
23659          languagesToLower[name.toLowerCase()] = options.languages[name];
23660        });
23661        _this.languages_ = languagesToLower;
23662      } else {
23663        _this.languages_ = Player.prototype.options_.languages;
23664      }
23665
23666      _this.resetCache_(); // Set poster
23667
23668
23669      _this.poster_ = options.poster || ''; // Set controls
23670
23671      _this.controls_ = !!options.controls; // Original tag settings stored in options
23672      // now remove immediately so native controls don't flash.
23673      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
23674
23675      tag.controls = false;
23676      tag.removeAttribute('controls');
23677      _this.changingSrc_ = false;
23678      _this.playCallbacks_ = [];
23679      _this.playTerminatedQueue_ = []; // the attribute overrides the option
23680
23681      if (tag.hasAttribute('autoplay')) {
23682        _this.autoplay(true);
23683      } else {
23684        // otherwise use the setter to validate and
23685        // set the correct value.
23686        _this.autoplay(_this.options_.autoplay);
23687      } // check plugins
23688
23689
23690      if (options.plugins) {
23691        Object.keys(options.plugins).forEach(function (name) {
23692          if (typeof _this[name] !== 'function') {
23693            throw new Error("plugin \"" + name + "\" does not exist");
23694          }
23695        });
23696      }
23697      /*
23698       * Store the internal state of scrubbing
23699       *
23700       * @private
23701       * @return {Boolean} True if the user is scrubbing
23702       */
23703
23704
23705      _this.scrubbing_ = false;
23706      _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
23707
23708      evented(assertThisInitialized(_this), {
23709        eventBusKey: 'el_'
23710      }); // listen to document and player fullscreenchange handlers so we receive those events
23711      // before a user can receive them so we can update isFullscreen appropriately.
23712      // make sure that we listen to fullscreenchange events before everything else to make sure that
23713      // our isFullscreen method is updated properly for internal components as well as external.
23714
23715      if (_this.fsApi_.requestFullscreen) {
23716        on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
23717
23718        _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
23719      }
23720
23721      if (_this.fluid_) {
23722        _this.on('playerreset', _this.updateStyleEl_);
23723      } // We also want to pass the original player options to each component and plugin
23724      // as well so they don't need to reach back into the player for options later.
23725      // We also need to do another copy of this.options_ so we don't end up with
23726      // an infinite loop.
23727
23728
23729      var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
23730
23731      if (options.plugins) {
23732        Object.keys(options.plugins).forEach(function (name) {
23733          _this[name](options.plugins[name]);
23734        });
23735      } // Enable debug mode to fire debugon event for all plugins.
23736
23737
23738      if (options.debug) {
23739        _this.debug(true);
23740      }
23741
23742      _this.options_.playerOptions = playerOptionsCopy;
23743      _this.middleware_ = [];
23744
23745      _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
23746
23747
23748      _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
23749      // set because the element doesn't exist yet.
23750
23751
23752      if (_this.controls()) {
23753        _this.addClass('vjs-controls-enabled');
23754      } else {
23755        _this.addClass('vjs-controls-disabled');
23756      } // Set ARIA label and region role depending on player type
23757
23758
23759      _this.el_.setAttribute('role', 'region');
23760
23761      if (_this.isAudio()) {
23762        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
23763      } else {
23764        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
23765      }
23766
23767      if (_this.isAudio()) {
23768        _this.addClass('vjs-audio');
23769      }
23770
23771      if (_this.flexNotSupported_()) {
23772        _this.addClass('vjs-no-flex');
23773      } // TODO: Make this smarter. Toggle user state between touching/mousing
23774      // using events, since devices can have both touch and mouse events.
23775      // TODO: Make this check be performed again when the window switches between monitors
23776      // (See https://github.com/videojs/video.js/issues/5683)
23777
23778
23779      if (TOUCH_ENABLED) {
23780        _this.addClass('vjs-touch-enabled');
23781      } // iOS Safari has broken hover handling
23782
23783
23784      if (!IS_IOS) {
23785        _this.addClass('vjs-workinghover');
23786      } // Make player easily findable by ID
23787
23788
23789      Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins
23790
23791      var majorVersion = version.split('.')[0];
23792
23793      _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
23794      // like the control bar show themselves if needed
23795
23796
23797      _this.userActive(true);
23798
23799      _this.reportUserActivity();
23800
23801      _this.one('play', _this.listenForUserActivity_);
23802
23803      _this.on('stageclick', _this.handleStageClick_);
23804
23805      _this.on('keydown', _this.handleKeyDown);
23806
23807      _this.breakpoints(_this.options_.breakpoints);
23808
23809      _this.responsive(_this.options_.responsive);
23810
23811      return _this;
23812    }
23813    /**
23814     * Destroys the video player and does any necessary cleanup.
23815     *
23816     * This is especially helpful if you are dynamically adding and removing videos
23817     * to/from the DOM.
23818     *
23819     * @fires Player#dispose
23820     */
23821
23822
23823    var _proto = Player.prototype;
23824
23825    _proto.dispose = function dispose() {
23826      var _this2 = this;
23827
23828      /**
23829       * Called when the player is being disposed of.
23830       *
23831       * @event Player#dispose
23832       * @type {EventTarget~Event}
23833       */
23834      this.trigger('dispose'); // prevent dispose from being called twice
23835
23836      this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
23837
23838      off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
23839      off(document, 'keydown', this.boundFullWindowOnEscKey_);
23840
23841      if (this.styleEl_ && this.styleEl_.parentNode) {
23842        this.styleEl_.parentNode.removeChild(this.styleEl_);
23843        this.styleEl_ = null;
23844      } // Kill reference to this player
23845
23846
23847      Player.players[this.id_] = null;
23848
23849      if (this.tag && this.tag.player) {
23850        this.tag.player = null;
23851      }
23852
23853      if (this.el_ && this.el_.player) {
23854        this.el_.player = null;
23855      }
23856
23857      if (this.tech_) {
23858        this.tech_.dispose();
23859        this.isPosterFromTech_ = false;
23860        this.poster_ = '';
23861      }
23862
23863      if (this.playerElIngest_) {
23864        this.playerElIngest_ = null;
23865      }
23866
23867      if (this.tag) {
23868        this.tag = null;
23869      }
23870
23871      clearCacheForPlayer(this); // remove all event handlers for track lists
23872      // all tracks and track listeners are removed on
23873      // tech dispose
23874
23875      ALL.names.forEach(function (name) {
23876        var props = ALL[name];
23877
23878        var list = _this2[props.getterName](); // if it is not a native list
23879        // we have to manually remove event listeners
23880
23881
23882        if (list && list.off) {
23883          list.off();
23884        }
23885      }); // the actual .el_ is removed here
23886
23887      _Component.prototype.dispose.call(this);
23888    }
23889    /**
23890     * Create the `Player`'s DOM element.
23891     *
23892     * @return {Element}
23893     *         The DOM element that gets created.
23894     */
23895    ;
23896
23897    _proto.createEl = function createEl() {
23898      var tag = this.tag;
23899      var el;
23900      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
23901      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
23902
23903      if (playerElIngest) {
23904        el = this.el_ = tag.parentNode;
23905      } else if (!divEmbed) {
23906        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
23907      } // Copy over all the attributes from the tag, including ID and class
23908      // ID will now reference player box, not the video tag
23909
23910
23911      var attrs = getAttributes(tag);
23912
23913      if (divEmbed) {
23914        el = this.el_ = tag;
23915        tag = this.tag = document.createElement('video');
23916
23917        while (el.children.length) {
23918          tag.appendChild(el.firstChild);
23919        }
23920
23921        if (!hasClass(el, 'video-js')) {
23922          addClass(el, 'video-js');
23923        }
23924
23925        el.appendChild(tag);
23926        playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
23927        // to our new `video` element. This will move things like
23928        // `src` or `controls` that were set via js before the player
23929        // was initialized.
23930
23931        Object.keys(el).forEach(function (k) {
23932          try {
23933            tag[k] = el[k];
23934          } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it
23935          }
23936        });
23937      } // set tabindex to -1 to remove the video element from the focus order
23938
23939
23940      tag.setAttribute('tabindex', '-1');
23941      attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
23942      // for the same issue with Chrome (on Windows) with JAWS.
23943      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
23944      // Note that we can't detect if JAWS is being used, but this ARIA attribute
23945      //  doesn't change behavior of IE11 or Chrome if JAWS is not being used
23946
23947      if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
23948        tag.setAttribute('role', 'application');
23949        attrs.role = 'application';
23950      } // Remove width/height attrs from tag so CSS can make it 100% width/height
23951
23952
23953      tag.removeAttribute('width');
23954      tag.removeAttribute('height');
23955
23956      if ('width' in attrs) {
23957        delete attrs.width;
23958      }
23959
23960      if ('height' in attrs) {
23961        delete attrs.height;
23962      }
23963
23964      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
23965        // don't copy over the class attribute to the player element when we're in a div embed
23966        // the class is already set up properly in the divEmbed case
23967        // and we want to make sure that the `video-js` class doesn't get lost
23968        if (!(divEmbed && attr === 'class')) {
23969          el.setAttribute(attr, attrs[attr]);
23970        }
23971
23972        if (divEmbed) {
23973          tag.setAttribute(attr, attrs[attr]);
23974        }
23975      }); // Update tag id/class for use as HTML5 playback tech
23976      // Might think we should do this after embedding in container so .vjs-tech class
23977      // doesn't flash 100% width/height, but class only applies with .video-js parent
23978
23979      tag.playerId = tag.id;
23980      tag.id += '_html5_api';
23981      tag.className = 'vjs-tech'; // Make player findable on elements
23982
23983      tag.player = el.player = this; // Default state of video is paused
23984
23985      this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
23986      // of the player in a way that's still overrideable by CSS, just like the
23987      // video element
23988
23989      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
23990        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
23991        var defaultsStyleEl = $('.vjs-styles-defaults');
23992        var head = $('head');
23993        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
23994      }
23995
23996      this.fill_ = false;
23997      this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
23998
23999      this.width(this.options_.width);
24000      this.height(this.options_.height);
24001      this.fill(this.options_.fill);
24002      this.fluid(this.options_.fluid);
24003      this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name
24004
24005      this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag,
24006      // because IE doesn't hide them completely from screen readers.
24007
24008      var links = tag.getElementsByTagName('a');
24009
24010      for (var i = 0; i < links.length; i++) {
24011        var linkEl = links.item(i);
24012        addClass(linkEl, 'vjs-hidden');
24013        linkEl.setAttribute('hidden', 'hidden');
24014      } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
24015      // keep track of the original for later so we can know if the source originally failed
24016
24017
24018      tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
24019
24020      if (tag.parentNode && !playerElIngest) {
24021        tag.parentNode.insertBefore(el, tag);
24022      } // insert the tag as the first child of the player element
24023      // then manually add it to the children array so that this.addChild
24024      // will work properly for other components
24025      //
24026      // Breaks iPhone, fixed in HTML5 setup.
24027
24028
24029      prependTo(tag, el);
24030      this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
24031      // if it's been set to something different to the doc
24032
24033      this.el_.setAttribute('lang', this.language_);
24034      this.el_ = el;
24035      return el;
24036    }
24037    /**
24038     * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
24039     * sets the `crossOrigin` property on the `<video>` tag to control the CORS
24040     * behavior.
24041     *
24042     * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
24043     *
24044     * @param {string} [value]
24045     *        The value to set the `Player`'s crossOrigin to. If an argument is
24046     *        given, must be one of `anonymous` or `use-credentials`.
24047     *
24048     * @return {string|undefined}
24049     *         - The current crossOrigin value of the `Player` when getting.
24050     *         - undefined when setting
24051     */
24052    ;
24053
24054    _proto.crossOrigin = function crossOrigin(value) {
24055      if (!value) {
24056        return this.techGet_('crossOrigin');
24057      }
24058
24059      if (value !== 'anonymous' && value !== 'use-credentials') {
24060        log.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\"");
24061        return;
24062      }
24063
24064      this.techCall_('setCrossOrigin', value);
24065      return;
24066    }
24067    /**
24068     * A getter/setter for the `Player`'s width. Returns the player's configured value.
24069     * To get the current width use `currentWidth()`.
24070     *
24071     * @param {number} [value]
24072     *        The value to set the `Player`'s width to.
24073     *
24074     * @return {number}
24075     *         The current width of the `Player` when getting.
24076     */
24077    ;
24078
24079    _proto.width = function width(value) {
24080      return this.dimension('width', value);
24081    }
24082    /**
24083     * A getter/setter for the `Player`'s height. Returns the player's configured value.
24084     * To get the current height use `currentheight()`.
24085     *
24086     * @param {number} [value]
24087     *        The value to set the `Player`'s heigth to.
24088     *
24089     * @return {number}
24090     *         The current height of the `Player` when getting.
24091     */
24092    ;
24093
24094    _proto.height = function height(value) {
24095      return this.dimension('height', value);
24096    }
24097    /**
24098     * A getter/setter for the `Player`'s width & height.
24099     *
24100     * @param {string} dimension
24101     *        This string can be:
24102     *        - 'width'
24103     *        - 'height'
24104     *
24105     * @param {number} [value]
24106     *        Value for dimension specified in the first argument.
24107     *
24108     * @return {number}
24109     *         The dimension arguments value when getting (width/height).
24110     */
24111    ;
24112
24113    _proto.dimension = function dimension(_dimension, value) {
24114      var privDimension = _dimension + '_';
24115
24116      if (value === undefined) {
24117        return this[privDimension] || 0;
24118      }
24119
24120      if (value === '' || value === 'auto') {
24121        // If an empty string is given, reset the dimension to be automatic
24122        this[privDimension] = undefined;
24123        this.updateStyleEl_();
24124        return;
24125      }
24126
24127      var parsedVal = parseFloat(value);
24128
24129      if (isNaN(parsedVal)) {
24130        log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
24131        return;
24132      }
24133
24134      this[privDimension] = parsedVal;
24135      this.updateStyleEl_();
24136    }
24137    /**
24138     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
24139     *
24140     * Turning this on will turn off fill mode.
24141     *
24142     * @param {boolean} [bool]
24143     *        - A value of true adds the class.
24144     *        - A value of false removes the class.
24145     *        - No value will be a getter.
24146     *
24147     * @return {boolean|undefined}
24148     *         - The value of fluid when getting.
24149     *         - `undefined` when setting.
24150     */
24151    ;
24152
24153    _proto.fluid = function fluid(bool) {
24154      if (bool === undefined) {
24155        return !!this.fluid_;
24156      }
24157
24158      this.fluid_ = !!bool;
24159
24160      if (isEvented(this)) {
24161        this.off('playerreset', this.updateStyleEl_);
24162      }
24163
24164      if (bool) {
24165        this.addClass('vjs-fluid');
24166        this.fill(false);
24167        addEventedCallback(function () {
24168          this.on('playerreset', this.updateStyleEl_);
24169        });
24170      } else {
24171        this.removeClass('vjs-fluid');
24172      }
24173
24174      this.updateStyleEl_();
24175    }
24176    /**
24177     * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
24178     *
24179     * Turning this on will turn off fluid mode.
24180     *
24181     * @param {boolean} [bool]
24182     *        - A value of true adds the class.
24183     *        - A value of false removes the class.
24184     *        - No value will be a getter.
24185     *
24186     * @return {boolean|undefined}
24187     *         - The value of fluid when getting.
24188     *         - `undefined` when setting.
24189     */
24190    ;
24191
24192    _proto.fill = function fill(bool) {
24193      if (bool === undefined) {
24194        return !!this.fill_;
24195      }
24196
24197      this.fill_ = !!bool;
24198
24199      if (bool) {
24200        this.addClass('vjs-fill');
24201        this.fluid(false);
24202      } else {
24203        this.removeClass('vjs-fill');
24204      }
24205    }
24206    /**
24207     * Get/Set the aspect ratio
24208     *
24209     * @param {string} [ratio]
24210     *        Aspect ratio for player
24211     *
24212     * @return {string|undefined}
24213     *         returns the current aspect ratio when getting
24214     */
24215
24216    /**
24217     * A getter/setter for the `Player`'s aspect ratio.
24218     *
24219     * @param {string} [ratio]
24220     *        The value to set the `Player`'s aspect ratio to.
24221     *
24222     * @return {string|undefined}
24223     *         - The current aspect ratio of the `Player` when getting.
24224     *         - undefined when setting
24225     */
24226    ;
24227
24228    _proto.aspectRatio = function aspectRatio(ratio) {
24229      if (ratio === undefined) {
24230        return this.aspectRatio_;
24231      } // Check for width:height format
24232
24233
24234      if (!/^\d+\:\d+$/.test(ratio)) {
24235        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
24236      }
24237
24238      this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
24239      // because in fixed mode you could calculate width and height yourself.
24240
24241      this.fluid(true);
24242      this.updateStyleEl_();
24243    }
24244    /**
24245     * Update styles of the `Player` element (height, width and aspect ratio).
24246     *
24247     * @private
24248     * @listens Tech#loadedmetadata
24249     */
24250    ;
24251
24252    _proto.updateStyleEl_ = function updateStyleEl_() {
24253      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
24254        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
24255
24256        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
24257
24258        var techEl = this.tech_ && this.tech_.el();
24259
24260        if (techEl) {
24261          if (_width >= 0) {
24262            techEl.width = _width;
24263          }
24264
24265          if (_height >= 0) {
vendor: 8,653 bytes, lines 24266-24477
24266            techEl.height = _height;
24267          }
24268        }
24269
24270        return;
24271      }
24272
24273      var width;
24274      var height;
24275      var aspectRatio;
24276      var idClass; // The aspect ratio is either used directly or to calculate width and height.
24277
24278      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
24279        // Use any aspectRatio that's been specifically set
24280        aspectRatio = this.aspectRatio_;
24281      } else if (this.videoWidth() > 0) {
24282        // Otherwise try to get the aspect ratio from the video metadata
24283        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
24284      } else {
24285        // Or use a default. The video element's is 2:1, but 16:9 is more common.
24286        aspectRatio = '16:9';
24287      } // Get the ratio as a decimal we can use to calculate dimensions
24288
24289
24290      var ratioParts = aspectRatio.split(':');
24291      var ratioMultiplier = ratioParts[1] / ratioParts[0];
24292
24293      if (this.width_ !== undefined) {
24294        // Use any width that's been specifically set
24295        width = this.width_;
24296      } else if (this.height_ !== undefined) {
24297        // Or calulate the width from the aspect ratio if a height has been set
24298        width = this.height_ / ratioMultiplier;
24299      } else {
24300        // Or use the video's metadata, or use the video el's default of 300
24301        width = this.videoWidth() || 300;
24302      }
24303
24304      if (this.height_ !== undefined) {
24305        // Use any height that's been specifically set
24306        height = this.height_;
24307      } else {
24308        // Otherwise calculate the height from the ratio and the width
24309        height = width * ratioMultiplier;
24310      } // Ensure the CSS class is valid by starting with an alpha character
24311
24312
24313      if (/^[^a-zA-Z]/.test(this.id())) {
24314        idClass = 'dimensions-' + this.id();
24315      } else {
24316        idClass = this.id() + '-dimensions';
24317      } // Ensure the right class is still on the player for the style element
24318
24319
24320      this.addClass(idClass);
24321      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    ");
24322    }
24323    /**
24324     * Load/Create an instance of playback {@link Tech} including element
24325     * and API methods. Then append the `Tech` element in `Player` as a child.
24326     *
24327     * @param {string} techName
24328     *        name of the playback technology
24329     *
24330     * @param {string} source
24331     *        video source
24332     *
24333     * @private
24334     */
24335    ;
24336
24337    _proto.loadTech_ = function loadTech_(techName, source) {
24338      var _this3 = this;
24339
24340      // Pause and remove current playback technology
24341      if (this.tech_) {
24342        this.unloadTech_();
24343      }
24344
24345      var titleTechName = toTitleCase(techName);
24346      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
24347
24348      if (titleTechName !== 'Html5' && this.tag) {
24349        Tech.getTech('Html5').disposeMediaElement(this.tag);
24350        this.tag.player = null;
24351        this.tag = null;
24352      }
24353
24354      this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
24355
24356      this.isReady_ = false; // if autoplay is a string we pass false to the tech
24357      // because the player is going to handle autoplay on `loadstart`
24358
24359      var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
24360
24361      var techOptions = {
24362        source: source,
24363        autoplay: autoplay,
24364        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
24365        'playerId': this.id(),
24366        'techId': this.id() + "_" + camelTechName + "_api",
24367        'playsinline': this.options_.playsinline,
24368        'preload': this.options_.preload,
24369        'loop': this.options_.loop,
24370        'disablePictureInPicture': this.options_.disablePictureInPicture,
24371        'muted': this.options_.muted,
24372        'poster': this.poster(),
24373        'language': this.language(),
24374        'playerElIngest': this.playerElIngest_ || false,
24375        'vtt.js': this.options_['vtt.js'],
24376        'canOverridePoster': !!this.options_.techCanOverridePoster,
24377        'enableSourceset': this.options_.enableSourceset,
24378        'Promise': this.options_.Promise
24379      };
24380      ALL.names.forEach(function (name) {
24381        var props = ALL[name];
24382        techOptions[props.getterName] = _this3[props.privateName];
24383      });
24384      assign(techOptions, this.options_[titleTechName]);
24385      assign(techOptions, this.options_[camelTechName]);
24386      assign(techOptions, this.options_[techName.toLowerCase()]);
24387
24388      if (this.tag) {
24389        techOptions.tag = this.tag;
24390      }
24391
24392      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
24393        techOptions.startTime = this.cache_.currentTime;
24394      } // Initialize tech instance
24395
24396
24397      var TechClass = Tech.getTech(techName);
24398
24399      if (!TechClass) {
24400        throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
24401      }
24402
24403      this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
24404
24405      this.tech_.ready(bind(this, this.handleTechReady_), true);
24406      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
24407
24408      TECH_EVENTS_RETRIGGER.forEach(function (event) {
24409        _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
24410      });
24411      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
24412        _this3.on(_this3.tech_, event, function (eventObj) {
24413          if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
24414            _this3.queuedCallbacks_.push({
24415              callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
24416              event: eventObj
24417            });
24418
24419            return;
24420          }
24421
24422          _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
24423        });
24424      });
24425      this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
24426      this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
24427      this.on(this.tech_, 'waiting', this.handleTechWaiting_);
24428      this.on(this.tech_, 'ended', this.handleTechEnded_);
24429      this.on(this.tech_, 'seeking', this.handleTechSeeking_);
24430      this.on(this.tech_, 'play', this.handleTechPlay_);
24431      this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
24432      this.on(this.tech_, 'pause', this.handleTechPause_);
24433      this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
24434      this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
24435      this.on(this.tech_, 'fullscreenerror', this.handleTechFullscreenError_);
24436      this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_);
24437      this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_);
24438      this.on(this.tech_, 'error', this.handleTechError_);
24439      this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
24440      this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
24441      this.on(this.tech_, 'textdata', this.handleTechTextData_);
24442      this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
24443      this.usingNativeControls(this.techGet_('controls'));
24444
24445      if (this.controls() && !this.usingNativeControls()) {
24446        this.addTechControlsListeners_();
24447      } // Add the tech element in the DOM if it was not already there
24448      // Make sure to not insert the original video element if using Html5
24449
24450
24451      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
24452        prependTo(this.tech_.el(), this.el());
24453      } // Get rid of the original video tag reference after the first tech is loaded
24454
24455
24456      if (this.tag) {
24457        this.tag.player = null;
24458        this.tag = null;
24459      }
24460    }
24461    /**
24462     * Unload and dispose of the current playback {@link Tech}.
24463     *
24464     * @private
24465     */
24466    ;
24467
24468    _proto.unloadTech_ = function unloadTech_() {
24469      var _this4 = this;
24470
24471      // Save the current text tracks so that we can reuse the same text tracks with the next tech
24472      ALL.names.forEach(function (name) {
24473        var props = ALL[name];
24474        _this4[props.privateName] = _this4[props.getterName]();
24475      });
24476      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
24477      this.isReady_ = false;
vendor: 9,755 bytes, lines 24478-24764
24478      this.tech_.dispose();
24479      this.tech_ = false;
24480
24481      if (this.isPosterFromTech_) {
24482        this.poster_ = '';
24483        this.trigger('posterchange');
24484      }
24485
24486      this.isPosterFromTech_ = false;
24487    }
24488    /**
24489     * Return a reference to the current {@link Tech}.
24490     * It will print a warning by default about the danger of using the tech directly
24491     * but any argument that is passed in will silence the warning.
24492     *
24493     * @param {*} [safety]
24494     *        Anything passed in to silence the warning
24495     *
24496     * @return {Tech}
24497     *         The Tech
24498     */
24499    ;
24500
24501    _proto.tech = function tech(safety) {
24502      if (safety === undefined) {
24503        log.warn('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');
24504      }
24505
24506      return this.tech_;
24507    }
24508    /**
24509     * Set up click and touch listeners for the playback element
24510     *
24511     * - On desktops: a click on the video itself will toggle playback
24512     * - On mobile devices: a click on the video toggles controls
24513     *   which is done by toggling the user state between active and
24514     *   inactive
24515     * - A tap can signal that a user has become active or has become inactive
24516     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
24517     *   quick tap should hide them again (signaling the user is in an inactive
24518     *   viewing state)
24519     * - In addition to this, we still want the user to be considered inactive after
24520     *   a few seconds of inactivity.
24521     *
24522     * > Note: the only part of iOS interaction we can't mimic with this setup
24523     * is a touch and hold on the video element counting as activity in order to
24524     * keep the controls showing, but that shouldn't be an issue. A touch and hold
24525     * on any controls will still keep the user active
24526     *
24527     * @private
24528     */
24529    ;
24530
24531    _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
24532      // Make sure to remove all the previous listeners in case we are called multiple times.
24533      this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
24534      // trigger mousedown/up.
24535      // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
24536      // Any touch events are set to block the mousedown event from happening
24537
24538      this.on(this.tech_, 'mouseup', this.handleTechClick_);
24539      this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
24540      // so we'll check if the controls were already showing before reporting user
24541      // activity
24542
24543      this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
24544      this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
24545      this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
24546      // listener cancels out any reportedUserActivity when setting userActive(false)
24547
24548      this.on(this.tech_, 'tap', this.handleTechTap_);
24549    }
24550    /**
24551     * Remove the listeners used for click and tap controls. This is needed for
24552     * toggling to controls disabled, where a tap/touch should do nothing.
24553     *
24554     * @private
24555     */
24556    ;
24557
24558    _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
24559      // We don't want to just use `this.off()` because there might be other needed
24560      // listeners added by techs that extend this.
24561      this.off(this.tech_, 'tap', this.handleTechTap_);
24562      this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
24563      this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
24564      this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
24565      this.off(this.tech_, 'mouseup', this.handleTechClick_);
24566      this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
24567    }
24568    /**
24569     * Player waits for the tech to be ready
24570     *
24571     * @private
24572     */
24573    ;
24574
24575    _proto.handleTechReady_ = function handleTechReady_() {
24576      this.triggerReady(); // Keep the same volume as before
24577
24578      if (this.cache_.volume) {
24579        this.techCall_('setVolume', this.cache_.volume);
24580      } // Look if the tech found a higher resolution poster while loading
24581
24582
24583      this.handleTechPosterChange_(); // Update the duration if available
24584
24585      this.handleTechDurationChange_();
24586    }
24587    /**
24588     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
24589     * function will also trigger {@link Player#firstplay} if it is the first loadstart
24590     * for a video.
24591     *
24592     * @fires Player#loadstart
24593     * @fires Player#firstplay
24594     * @listens Tech#loadstart
24595     * @private
24596     */
24597    ;
24598
24599    _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
24600      // TODO: Update to use `emptied` event instead. See #1277.
24601      this.removeClass('vjs-ended');
24602      this.removeClass('vjs-seeking'); // reset the error state
24603
24604      this.error(null); // Update the duration
24605
24606      this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
24607      // The firstplay event relies on both the play and loadstart events
24608      // which can happen in any order for a new source
24609
24610      if (!this.paused()) {
24611        /**
24612         * Fired when the user agent begins looking for media data
24613         *
24614         * @event Player#loadstart
24615         * @type {EventTarget~Event}
24616         */
24617        this.trigger('loadstart');
24618        this.trigger('firstplay');
24619      } else {
24620        // reset the hasStarted state
24621        this.hasStarted(false);
24622        this.trigger('loadstart');
24623      } // autoplay happens after loadstart for the browser,
24624      // so we mimic that behavior
24625
24626
24627      this.manualAutoplay_(this.autoplay());
24628    }
24629    /**
24630     * Handle autoplay string values, rather than the typical boolean
24631     * values that should be handled by the tech. Note that this is not
24632     * part of any specification. Valid values and what they do can be
24633     * found on the autoplay getter at Player#autoplay()
24634     */
24635    ;
24636
24637    _proto.manualAutoplay_ = function manualAutoplay_(type) {
24638      var _this5 = this;
24639
24640      if (!this.tech_ || typeof type !== 'string') {
24641        return;
24642      }
24643
24644      var muted = function muted() {
24645        var previouslyMuted = _this5.muted();
24646
24647        _this5.muted(true);
24648
24649        var restoreMuted = function restoreMuted() {
24650          _this5.muted(previouslyMuted);
24651        }; // restore muted on play terminatation
24652
24653
24654        _this5.playTerminatedQueue_.push(restoreMuted);
24655
24656        var mutedPromise = _this5.play();
24657
24658        if (!isPromise(mutedPromise)) {
24659          return;
24660        }
24661
24662        return mutedPromise["catch"](restoreMuted);
24663      };
24664
24665      var promise; // if muted defaults to true
24666      // the only thing we can do is call play
24667
24668      if (type === 'any' && this.muted() !== true) {
24669        promise = this.play();
24670
24671        if (isPromise(promise)) {
24672          promise = promise["catch"](muted);
24673        }
24674      } else if (type === 'muted' && this.muted() !== true) {
24675        promise = muted();
24676      } else {
24677        promise = this.play();
24678      }
24679
24680      if (!isPromise(promise)) {
24681        return;
24682      }
24683
24684      return promise.then(function () {
24685        _this5.trigger({
24686          type: 'autoplay-success',
24687          autoplay: type
24688        });
24689      })["catch"](function (e) {
24690        _this5.trigger({
24691          type: 'autoplay-failure',
24692          autoplay: type
24693        });
24694      });
24695    }
24696    /**
24697     * Update the internal source caches so that we return the correct source from
24698     * `src()`, `currentSource()`, and `currentSources()`.
24699     *
24700     * > Note: `currentSources` will not be updated if the source that is passed in exists
24701     *         in the current `currentSources` cache.
24702     *
24703     *
24704     * @param {Tech~SourceObject} srcObj
24705     *        A string or object source to update our caches to.
24706     */
24707    ;
24708
24709    _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
24710      if (srcObj === void 0) {
24711        srcObj = '';
24712      }
24713
24714      var src = srcObj;
24715      var type = '';
24716
24717      if (typeof src !== 'string') {
24718        src = srcObj.src;
24719        type = srcObj.type;
24720      } // make sure all the caches are set to default values
24721      // to prevent null checking
24722
24723
24724      this.cache_.source = this.cache_.source || {};
24725      this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
24726
24727      if (src && !type) {
24728        type = findMimetype(this, src);
24729      } // update `currentSource` cache always
24730
24731
24732      this.cache_.source = mergeOptions({}, srcObj, {
24733        src: src,
24734        type: type
24735      });
24736      var matchingSources = this.cache_.sources.filter(function (s) {
24737        return s.src && s.src === src;
24738      });
24739      var sourceElSources = [];
24740      var sourceEls = this.$$('source');
24741      var matchingSourceEls = [];
24742
24743      for (var i = 0; i < sourceEls.length; i++) {
24744        var sourceObj = getAttributes(sourceEls[i]);
24745        sourceElSources.push(sourceObj);
24746
24747        if (sourceObj.src && sourceObj.src === src) {
24748          matchingSourceEls.push(sourceObj.src);
24749        }
24750      } // if we have matching source els but not matching sources
24751      // the current source cache is not up to date
24752
24753
24754      if (matchingSourceEls.length && !matchingSources.length) {
24755        this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
24756        // sources cache to the `currentSource` cache
24757      } else if (!matchingSources.length) {
24758        this.cache_.sources = [this.cache_.source];
24759      } // update the tech `src` cache
24760
24761
24762      this.cache_.src = src;
24763    }
24764    /**
24765     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
24766     * causing the media element to reload.
24767     *
24768     * It will fire for the initial source and each subsequent source.
24769     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
24770     *
24771     * The event object for this event contains a `src` property that will contain the source
24772     * that was available when the event was triggered. This is generally only necessary if Video.js
24773     * is switching techs while the source was being changed.
24774     *
24775     * It is also fired when `load` is called on the player (or media element)
24776     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
24777     * says that the resource selection algorithm needs to be aborted and restarted.
24778     * In this case, it is very likely that the `src` property will be set to the
24779     * empty string `""` to indicate we do not know what the source will be but
24780     * that it is changing.
24781     *
24782     * *This event is currently still experimental and may change in minor releases.*
24783     * __To use this, pass `enableSourceset` option to the player.__
24784     *
24785     * @event Player#sourceset
24786     * @type {EventTarget~Event}
24787     * @prop {string} src
24788     *                The source url available when the `sourceset` was triggered.
24789     *                It will be an empty string if we cannot know what the source is
24790     *                but know that the source will change.
24791     */
24792
24793    /**
24794     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
24795     *
24796     * @fires Player#sourceset
24797     * @listens Tech#sourceset
24798     * @private
24799     */
24800    ;
24801
24802    _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
24803      var _this6 = this;
24804
24805      // only update the source cache when the source
24806      // was not updated using the player api
24807      if (!this.changingSrc_) {
24808        var updateSourceCaches = function updateSourceCaches(src) {
24809          return _this6.updateSourceCaches_(src);
24810        };
24811
24812        var playerSrc = this.currentSource().src;
24813        var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
24814
24815        if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
24816          // if both the tech source and the player source were updated we assume
24817          // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
24818          if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
24819            updateSourceCaches = function updateSourceCaches() {};
24820          }
24821        } // update the source to the initial source right away
24822        // in some cases this will be empty string
24823
24824
24825        updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
24826        // wait for a `loadstart` to update the cache to `currentSrc`.
24827        // If a sourceset happens before a `loadstart`, we reset the state
24828
24829        if (!event.src) {
24830          this.tech_.any(['sourceset', 'loadstart'], function (e) {
24831            // if a sourceset happens before a `loadstart` there
24832            // is nothing to do as this `handleTechSourceset_`
24833            // will be called again and this will be handled there.
24834            if (e.type === 'sourceset') {
24835              return;
24836            }
24837
24838            var techSrc = _this6.techGet('currentSrc');
24839
24840            _this6.lastSource_.tech = techSrc;
24841
24842            _this6.updateSourceCaches_(techSrc);
24843          });
24844        }
24845      }
24846
24847      this.lastSource_ = {
24848        player: this.currentSource().src,
24849        tech: event.src
24850      };
24851      this.trigger({
24852        src: event.src,
24853        type: 'sourceset'
24854      });
24855    }
24856    /**
24857     * Add/remove the vjs-has-started class
24858     *
24859     * @fires Player#firstplay
24860     *
24861     * @param {boolean} request
24862     *        - true: adds the class
24863     *        - false: remove the class
24864     *
24865     * @return {boolean}
24866     *         the boolean value of hasStarted_
24867     */
24868    ;
24869
24870    _proto.hasStarted = function hasStarted(request) {
24871      if (request === undefined) {
24872        // act as getter, if we have no request to change
24873        return this.hasStarted_;
24874      }
24875
24876      if (request === this.hasStarted_) {
24877        return;
24878      }
24879
24880      this.hasStarted_ = request;
24881
24882      if (this.hasStarted_) {
24883        this.addClass('vjs-has-started');
24884        this.trigger('firstplay');
24885      } else {
24886        this.removeClass('vjs-has-started');
24887      }
24888    }
24889    /**
24890     * Fired whenever the media begins or resumes playback
24891     *
24892     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
24893     * @fires Player#play
24894     * @listens Tech#play
24895     * @private
24896     */
24897    ;
24898
24899    _proto.handleTechPlay_ = function handleTechPlay_() {
24900      this.removeClass('vjs-ended');
24901      this.removeClass('vjs-paused');
24902      this.addClass('vjs-playing'); // hide the poster when the user hits play
24903
24904      this.hasStarted(true);
24905      /**
24906       * Triggered whenever an {@link Tech#play} event happens. Indicates that
24907       * playback has started or resumed.
24908       *
24909       * @event Player#play
24910       * @type {EventTarget~Event}
24911       */
24912
24913      this.trigger('play');
24914    }
24915    /**
24916     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
24917     *
24918     * If there were any events queued while the playback rate was zero, fire
24919     * those events now.
24920     *
24921     * @private
24922     * @method Player#handleTechRateChange_
24923     * @fires Player#ratechange
24924     * @listens Tech#ratechange
24925     */
24926    ;
24927
24928    _proto.handleTechRateChange_ = function handleTechRateChange_() {
24929      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
24930        this.queuedCallbacks_.forEach(function (queued) {
24931          return queued.callback(queued.event);
24932        });
24933        this.queuedCallbacks_ = [];
24934      }
24935
24936      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
24937      /**
24938       * Fires when the playing speed of the audio/video is changed
24939       *
24940       * @event Player#ratechange
24941       * @type {event}
24942       */
24943
24944      this.trigger('ratechange');
24945    }
24946    /**
24947     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
24948     *
24949     * @fires Player#waiting
24950     * @listens Tech#waiting
24951     * @private
24952     */
24953    ;
24954
24955    _proto.handleTechWaiting_ = function handleTechWaiting_() {
24956      var _this7 = this;
24957
24958      this.addClass('vjs-waiting');
24959      /**
24960       * A readyState change on the DOM element has caused playback to stop.
24961       *
24962       * @event Player#waiting
24963       * @type {EventTarget~Event}
24964       */
24965
24966      this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
24967      // premature removal of the waiting class, wait for the time to change.
24968
24969      var timeWhenWaiting = this.currentTime();
24970
24971      var timeUpdateListener = function timeUpdateListener() {
24972        if (timeWhenWaiting !== _this7.currentTime()) {
24973          _this7.removeClass('vjs-waiting');
24974
24975          _this7.off('timeupdate', timeUpdateListener);
24976        }
24977      };
24978
24979      this.on('timeupdate', timeUpdateListener);
24980    }
24981    /**
24982     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
24983     * > Note: This is not consistent between browsers. See #1351
24984     *
24985     * @fires Player#canplay
24986     * @listens Tech#canplay
24987     * @private
24988     */
24989    ;
24990
24991    _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
24992      this.removeClass('vjs-waiting');
24993      /**
24994       * The media has a readyState of HAVE_FUTURE_DATA or greater.
24995       *
24996       * @event Player#canplay
24997       * @type {EventTarget~Event}
24998       */
24999
25000      this.trigger('canplay');
25001    }
25002    /**
25003     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
25004     *
25005     * @fires Player#canplaythrough
25006     * @listens Tech#canplaythrough
25007     * @private
25008     */
25009    ;
25010
25011    _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
25012      this.removeClass('vjs-waiting');
25013      /**
25014       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
25015       * entire media file can be played without buffering.
25016       *
25017       * @event Player#canplaythrough
25018       * @type {EventTarget~Event}
25019       */
25020
25021      this.trigger('canplaythrough');
25022    }
25023    /**
25024     * Retrigger the `playing` event that was triggered by the {@link Tech}.
25025     *
25026     * @fires Player#playing
25027     * @listens Tech#playing
25028     * @private
25029     */
25030    ;
25031
25032    _proto.handleTechPlaying_ = function handleTechPlaying_() {
25033      this.removeClass('vjs-waiting');
25034      /**
25035       * The media is no longer blocked from playback, and has started playing.
25036       *
25037       * @event Player#playing
25038       * @type {EventTarget~Event}
25039       */
25040
25041      this.trigger('playing');
25042    }
25043    /**
25044     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
25045     *
25046     * @fires Player#seeking
25047     * @listens Tech#seeking
25048     * @private
25049     */
25050    ;
25051
25052    _proto.handleTechSeeking_ = function handleTechSeeking_() {
25053      this.addClass('vjs-seeking');
25054      /**
25055       * Fired whenever the player is jumping to a new time
25056       *
25057       * @event Player#seeking
25058       * @type {EventTarget~Event}
25059       */
25060
25061      this.trigger('seeking');
25062    }
25063    /**
25064     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
25065     *
25066     * @fires Player#seeked
25067     * @listens Tech#seeked
25068     * @private
25069     */
25070    ;
25071
25072    _proto.handleTechSeeked_ = function handleTechSeeked_() {
25073      this.removeClass('vjs-seeking');
25074      this.removeClass('vjs-ended');
25075      /**
25076       * Fired when the player has finished jumping to a new time
25077       *
25078       * @event Player#seeked
25079       * @type {EventTarget~Event}
25080       */
25081
25082      this.trigger('seeked');
25083    }
25084    /**
25085     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
25086     *
25087     * @fires Player#firstplay
25088     * @listens Tech#firstplay
25089     * @deprecated As of 6.0 firstplay event is deprecated.
25090     *             As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
25091     * @private
25092     */
25093    ;
25094
25095    _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
25096      // If the first starttime attribute is specified
25097      // then we will start at the given offset in seconds
25098      if (this.options_.starttime) {
25099        log.warn('Passing the `starttime` option to the player will be deprecated 
vendor: 25,490 bytes, lines 25099-25984
25099in 6.0');
25100        this.currentTime(this.options_.starttime);
25101      }
25102
25103      this.addClass('vjs-has-started');
25104      /**
25105       * Fired the first time a video is played. Not part of the HLS spec, and this is
25106       * probably not the best implementation yet, so use sparingly. If you don't have a
25107       * reason to prevent playback, use `myPlayer.one('play');` instead.
25108       *
25109       * @event Player#firstplay
25110       * @deprecated As of 6.0 firstplay event is deprecated.
25111       * @type {EventTarget~Event}
25112       */
25113
25114      this.trigger('firstplay');
25115    }
25116    /**
25117     * Retrigger the `pause` event that was triggered by the {@link Tech}.
25118     *
25119     * @fires Player#pause
25120     * @listens Tech#pause
25121     * @private
25122     */
25123    ;
25124
25125    _proto.handleTechPause_ = function handleTechPause_() {
25126      this.removeClass('vjs-playing');
25127      this.addClass('vjs-paused');
25128      /**
25129       * Fired whenever the media has been paused
25130       *
25131       * @event Player#pause
25132       * @type {EventTarget~Event}
25133       */
25134
25135      this.trigger('pause');
25136    }
25137    /**
25138     * Retrigger the `ended` event that was triggered by the {@link Tech}.
25139     *
25140     * @fires Player#ended
25141     * @listens Tech#ended
25142     * @private
25143     */
25144    ;
25145
25146    _proto.handleTechEnded_ = function handleTechEnded_() {
25147      this.addClass('vjs-ended');
25148
25149      if (this.options_.loop) {
25150        this.currentTime(0);
25151        this.play();
25152      } else if (!this.paused()) {
25153        this.pause();
25154      }
25155      /**
25156       * Fired when the end of the media resource is reached (currentTime == duration)
25157       *
25158       * @event Player#ended
25159       * @type {EventTarget~Event}
25160       */
25161
25162
25163      this.trigger('ended');
25164    }
25165    /**
25166     * Fired when the duration of the media resource is first known or changed
25167     *
25168     * @listens Tech#durationchange
25169     * @private
25170     */
25171    ;
25172
25173    _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
25174      this.duration(this.techGet_('duration'));
25175    }
25176    /**
25177     * Handle a click on the media element to play/pause
25178     *
25179     * @param {EventTarget~Event} event
25180     *        the event that caused this function to trigger
25181     *
25182     * @listens Tech#mouseup
25183     * @private
25184     */
25185    ;
25186
25187    _proto.handleTechClick_ = function handleTechClick_(event) {
25188      if (!isSingleLeftClick(event)) {
25189        return;
25190      } // When controls are disabled a click should not toggle playback because
25191      // the click is considered a control
25192
25193
25194      if (!this.controls_) {
25195        return;
25196      }
25197
25198      if (this.paused()) {
25199        silencePromise(this.play());
25200      } else {
25201        this.pause();
25202      }
25203    }
25204    /**
25205     * Handle a double-click on the media element to enter/exit fullscreen
25206     *
25207     * @param {EventTarget~Event} event
25208     *        the event that caused this function to trigger
25209     *
25210     * @listens Tech#dblclick
25211     * @private
25212     */
25213    ;
25214
25215    _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
25216      if (!this.controls_) {
25217        return;
25218      } // we do not want to toggle fullscreen state
25219      // when double-clicking inside a control bar or a modal
25220
25221
25222      var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
25223        return el.contains(event.target);
25224      });
25225
25226      if (!inAllowedEls) {
25227        /*
25228         * options.userActions.doubleClick
25229         *
25230         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
25231         * Set to `false` to disable double-click handling
25232         * Set to a function to substitute an external double-click handler
25233         */
25234        if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
25235          if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
25236            this.options_.userActions.doubleClick.call(this, event);
25237          } else if (this.isFullscreen()) {
25238            this.exitFullscreen();
25239          } else {
25240            this.requestFullscreen();
25241          }
25242        }
25243      }
25244    }
25245    /**
25246     * Handle a tap on the media element. It will toggle the user
25247     * activity state, which hides and shows the controls.
25248     *
25249     * @listens Tech#tap
25250     * @private
25251     */
25252    ;
25253
25254    _proto.handleTechTap_ = function handleTechTap_() {
25255      this.userActive(!this.userActive());
25256    }
25257    /**
25258     * Handle touch to start
25259     *
25260     * @listens Tech#touchstart
25261     * @private
25262     */
25263    ;
25264
25265    _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
25266      this.userWasActive = this.userActive();
25267    }
25268    /**
25269     * Handle touch to move
25270     *
25271     * @listens Tech#touchmove
25272     * @private
25273     */
25274    ;
25275
25276    _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
25277      if (this.userWasActive) {
25278        this.reportUserActivity();
25279      }
25280    }
25281    /**
25282     * Handle touch to end
25283     *
25284     * @param {EventTarget~Event} event
25285     *        the touchend event that triggered
25286     *        this function
25287     *
25288     * @listens Tech#touchend
25289     * @private
25290     */
25291    ;
25292
25293    _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
25294      // Stop the mouse events from also happening
25295      event.preventDefault();
25296    }
25297    /**
25298     * native click events on the SWF aren't triggered on IE11, Win8.1RT
25299     * use stageclick events triggered from inside the SWF instead
25300     *
25301     * @private
25302     * @listens stageclick
25303     */
25304    ;
25305
25306    _proto.handleStageClick_ = function handleStageClick_() {
25307      this.reportUserActivity();
25308    }
25309    /**
25310     * @private
25311     */
25312    ;
25313
25314    _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
25315      if (this.isFullscreen()) {
25316        this.addClass('vjs-fullscreen');
25317      } else {
25318        this.removeClass('vjs-fullscreen');
25319      }
25320    }
25321    /**
25322     * when the document fschange event triggers it calls this
25323     */
25324    ;
25325
25326    _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
25327      var targetPlayer = e.target.player; // if another player was fullscreen
25328      // do a null check for targetPlayer because older firefox's would put document as e.target
25329
25330      if (targetPlayer && targetPlayer !== this) {
25331        return;
25332      }
25333
25334      var el = this.el();
25335      var isFs = document[this.fsApi_.fullscreenElement] === el;
25336
25337      if (!isFs && el.matches) {
25338        isFs = el.matches(':' + this.fsApi_.fullscreen);
25339      } else if (!isFs && el.msMatchesSelector) {
25340        isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
25341      }
25342
25343      this.isFullscreen(isFs);
25344    }
25345    /**
25346     * Handle Tech Fullscreen Change
25347     *
25348     * @param {EventTarget~Event} event
25349     *        the fullscreenchange event that triggered this function
25350     *
25351     * @param {Object} data
25352     *        the data that was sent with the event
25353     *
25354     * @private
25355     * @listens Tech#fullscreenchange
25356     * @fires Player#fullscreenchange
25357     */
25358    ;
25359
25360    _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
25361      if (data) {
25362        if (data.nativeIOSFullscreen) {
25363          this.toggleClass('vjs-ios-native-fs');
25364        }
25365
25366        this.isFullscreen(data.isFullscreen);
25367      }
25368    };
25369
25370    _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) {
25371      this.trigger('fullscreenerror', err);
25372    }
25373    /**
25374     * @private
25375     */
25376    ;
25377
25378    _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() {
25379      if (this.isInPictureInPicture()) {
25380        this.addClass('vjs-picture-in-picture');
25381      } else {
25382        this.removeClass('vjs-picture-in-picture');
25383      }
25384    }
25385    /**
25386     * Handle Tech Enter Picture-in-Picture.
25387     *
25388     * @param {EventTarget~Event} event
25389     *        the enterpictureinpicture event that triggered this function
25390     *
25391     * @private
25392     * @listens Tech#enterpictureinpicture
25393     */
25394    ;
25395
25396    _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) {
25397      this.isInPictureInPicture(true);
25398    }
25399    /**
25400     * Handle Tech Leave Picture-in-Picture.
25401     *
25402     * @param {EventTarget~Event} event
25403     *        the leavepictureinpicture event that triggered this function
25404     *
25405     * @private
25406     * @listens Tech#leavepictureinpicture
25407     */
25408    ;
25409
25410    _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) {
25411      this.isInPictureInPicture(false);
25412    }
25413    /**
25414     * Fires when an error occurred during the loading of an audio/video.
25415     *
25416     * @private
25417     * @listens Tech#error
25418     */
25419    ;
25420
25421    _proto.handleTechError_ = function handleTechError_() {
25422      var error = this.tech_.error();
25423      this.error(error);
25424    }
25425    /**
25426     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
25427     *
25428     * @fires Player#textdata
25429     * @listens Tech#textdata
25430     * @private
25431     */
25432    ;
25433
25434    _proto.handleTechTextData_ = function handleTechTextData_() {
25435      var data = null;
25436
25437      if (arguments.length > 1) {
25438        data = arguments[1];
25439      }
25440      /**
25441       * Fires when we get a textdata event from tech
25442       *
25443       * @event Player#textdata
25444       * @type {EventTarget~Event}
25445       */
25446
25447
25448      this.trigger('textdata', data);
25449    }
25450    /**
25451     * Get object for cached values.
25452     *
25453     * @return {Object}
25454     *         get the current object cache
25455     */
25456    ;
25457
25458    _proto.getCache = function getCache() {
25459      return this.cache_;
25460    }
25461    /**
25462     * Resets the internal cache object.
25463     *
25464     * Using this function outside the player constructor or reset method may
25465     * have unintended side-effects.
25466     *
25467     * @private
25468     */
25469    ;
25470
25471    _proto.resetCache_ = function resetCache_() {
25472      this.cache_ = {
25473        // Right now, the currentTime is not _really_ cached because it is always
25474        // retrieved from the tech (see: currentTime). However, for completeness,
25475        // we set it to zero here to ensure that if we do start actually caching
25476        // it, we reset it along with everything else.
25477        currentTime: 0,
25478        initTime: 0,
25479        inactivityTimeout: this.options_.inactivityTimeout,
25480        duration: NaN,
25481        lastVolume: 1,
25482        lastPlaybackRate: this.defaultPlaybackRate(),
25483        media: null,
25484        src: '',
25485        source: {},
25486        sources: [],
25487        volume: 1
25488      };
25489    }
25490    /**
25491     * Pass values to the playback tech
25492     *
25493     * @param {string} [method]
25494     *        the method to call
25495     *
25496     * @param {Object} arg
25497     *        the argument to pass
25498     *
25499     * @private
25500     */
25501    ;
25502
25503    _proto.techCall_ = function techCall_(method, arg) {
25504      // If it's not ready yet, call method when it is
25505      this.ready(function () {
25506        if (method in allowedSetters) {
25507          return set(this.middleware_, this.tech_, method, arg);
25508        } else if (method in allowedMediators) {
25509          return mediate(this.middleware_, this.tech_, method, arg);
25510        }
25511
25512        try {
25513          if (this.tech_) {
25514            this.tech_[method](arg);
25515          }
25516        } catch (e) {
25517          log(e);
25518          throw e;
25519        }
25520      }, true);
25521    }
25522    /**
25523     * Get calls can't wait for the tech, and sometimes don't need to.
25524     *
25525     * @param {string} method
25526     *        Tech method
25527     *
25528     * @return {Function|undefined}
25529     *         the method or undefined
25530     *
25531     * @private
25532     */
25533    ;
25534
25535    _proto.techGet_ = function techGet_(method) {
25536      if (!this.tech_ || !this.tech_.isReady_) {
25537        return;
25538      }
25539
25540      if (method in allowedGetters) {
25541        return get(this.middleware_, this.tech_, method);
25542      } else if (method in allowedMediators) {
25543        return mediate(this.middleware_, this.tech_, method);
25544      } // Flash likes to die and reload when you hide or reposition it.
25545      // In these cases the object methods go away and we get errors.
25546      // When that happens we'll catch the errors and inform tech that it's not ready any more.
25547
25548
25549      try {
25550        return this.tech_[method]();
25551      } catch (e) {
25552        // When building additional tech libs, an expected method may not be defined yet
25553        if (this.tech_[method] === undefined) {
25554          log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
25555          throw e;
25556        } // When a method isn't available on the object it throws a TypeError
25557
25558
25559        if (e.name === 'TypeError') {
25560          log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
25561          this.tech_.isReady_ = false;
25562          throw e;
25563        } // If error unknown, just log and throw
25564
25565
25566        log(e);
25567        throw e;
25568      }
25569    }
25570    /**
25571     * Attempt to begin playback at the first opportunity.
25572     *
25573     * @return {Promise|undefined}
25574     *         Returns a promise if the browser supports Promises (or one
25575     *         was passed in as an option). This promise will be resolved on
25576     *         the return value of play. If this is undefined it will fulfill the
25577     *         promise chain otherwise the promise chain will be fulfilled when
25578     *         the promise from play is fulfilled.
25579     */
25580    ;
25581
25582    _proto.play = function play() {
25583      var _this8 = this;
25584
25585      var PromiseClass = this.options_.Promise || window$1.Promise;
25586
25587      if (PromiseClass) {
25588        return new PromiseClass(function (resolve) {
25589          _this8.play_(resolve);
25590        });
25591      }
25592
25593      return this.play_();
25594    }
25595    /**
25596     * The actual logic for play, takes a callback that will be resolved on the
25597     * return value of play. This allows us to resolve to the play promise if there
25598     * is one on modern browsers.
25599     *
25600     * @private
25601     * @param {Function} [callback]
25602     *        The callback that should be called when the techs play is actually called
25603     */
25604    ;
25605
25606    _proto.play_ = function play_(callback) {
25607      var _this9 = this;
25608
25609      if (callback === void 0) {
25610        callback = silencePromise;
25611      }
25612
25613      this.playCallbacks_.push(callback);
25614      var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
25615
25616      if (this.waitToPlay_) {
25617        this.off(['ready', 'loadstart'], this.waitToPlay_);
25618        this.waitToPlay_ = null;
25619      } // if the player/tech is not ready or the src itself is not ready
25620      // queue up a call to play on `ready` or `loadstart`
25621
25622
25623      if (!this.isReady_ || !isSrcReady) {
25624        this.waitToPlay_ = function (e) {
25625          _this9.play_();
25626        };
25627
25628        this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
25629        // in that case, we need to prime the video element by calling load so it'll be ready in time
25630
25631        if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
25632          this.load();
25633        }
25634
25635        return;
25636      } // If the player/tech is ready and we have a source, we can attempt playback.
25637
25638
25639      var val = this.techGet_('play'); // play was terminated if the returned value is null
25640
25641      if (val === null) {
25642        this.runPlayTerminatedQueue_();
25643      } else {
25644        this.runPlayCallbacks_(val);
25645      }
25646    }
25647    /**
25648     * These functions will be run when if play is terminated. If play
25649     * runPlayCallbacks_ is run these function will not be run. This allows us
25650     * to differenciate between a terminated play and an actual call to play.
25651     */
25652    ;
25653
25654    _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
25655      var queue = this.playTerminatedQueue_.slice(0);
25656      this.playTerminatedQueue_ = [];
25657      queue.forEach(function (q) {
25658        q();
25659      });
25660    }
25661    /**
25662     * When a callback to play is delayed we have to run these
25663     * callbacks when play is actually called on the tech. This function
25664     * runs the callbacks that were delayed and accepts the return value
25665     * from the tech.
25666     *
25667     * @param {undefined|Promise} val
25668     *        The return value from the tech.
25669     */
25670    ;
25671
25672    _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
25673      var callbacks = this.playCallbacks_.slice(0);
25674      this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
25675
25676      this.playTerminatedQueue_ = [];
25677      callbacks.forEach(function (cb) {
25678        cb(val);
25679      });
25680    }
25681    /**
25682     * Pause the video playback
25683     *
25684     * @return {Player}
25685     *         A reference to the player object this function was called on
25686     */
25687    ;
25688
25689    _proto.pause = function pause() {
25690      this.techCall_('pause');
25691    }
25692    /**
25693     * Check if the player is paused or has yet to play
25694     *
25695     * @return {boolean}
25696     *         - false: if the media is currently playing
25697     *         - true: if media is not currently playing
25698     */
25699    ;
25700
25701    _proto.paused = function paused() {
25702      // The initial state of paused should be true (in Safari it's actually false)
25703      return this.techGet_('paused') === false ? false : true;
25704    }
25705    /**
25706     * Get a TimeRange object representing the current ranges of time that the user
25707     * has played.
25708     *
25709     * @return {TimeRange}
25710     *         A time range object that represents all the increments of time that have
25711     *         been played.
25712     */
25713    ;
25714
25715    _proto.played = function played() {
25716      return this.techGet_('played') || createTimeRanges(0, 0);
25717    }
25718    /**
25719     * Returns whether or not the user is "scrubbing". Scrubbing is
25720     * when the user has clicked the progress bar handle and is
25721     * dragging it along the progress bar.
25722     *
25723     * @param {boolean} [isScrubbing]
25724     *        whether the user is or is not scrubbing
25725     *
25726     * @return {boolean}
25727     *         The value of scrubbing when getting
25728     */
25729    ;
25730
25731    _proto.scrubbing = function scrubbing(isScrubbing) {
25732      if (typeof isScrubbing === 'undefined') {
25733        return this.scrubbing_;
25734      }
25735
25736      this.scrubbing_ = !!isScrubbing;
25737      this.techCall_('setScrubbing', this.scrubbing_);
25738
25739      if (isScrubbing) {
25740        this.addClass('vjs-scrubbing');
25741      } else {
25742        this.removeClass('vjs-scrubbing');
25743      }
25744    }
25745    /**
25746     * Get or set the current time (in seconds)
25747     *
25748     * @param {number|string} [seconds]
25749     *        The time to seek to in seconds
25750     *
25751     * @return {number}
25752     *         - the current time in seconds when getting
25753     */
25754    ;
25755
25756    _proto.currentTime = function currentTime(seconds) {
25757      if (typeof seconds !== 'undefined') {
25758        if (seconds < 0) {
25759          seconds = 0;
25760        }
25761
25762        if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
25763          this.cache_.initTime = seconds;
25764          this.off('canplay', this.applyInitTime_);
25765          this.one('canplay', this.applyInitTime_);
25766          return;
25767        }
25768
25769        this.techCall_('setCurrentTime', seconds);
25770        this.cache_.initTime = 0;
25771        return;
25772      } // cache last currentTime and return. default to 0 seconds
25773      //
25774      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
25775      // currentTime when scrubbing, but may not provide much performance benefit afterall.
25776      // Should be tested. Also something has to read the actual current time or the cache will
25777      // never get updated.
25778
25779
25780      this.cache_.currentTime = this.techGet_('currentTime') || 0;
25781      return this.cache_.currentTime;
25782    }
25783    /**
25784     * Apply the value of initTime stored in cache as currentTime.
25785     *
25786     * @private
25787     */
25788    ;
25789
25790    _proto.applyInitTime_ = function applyInitTime_() {
25791      this.currentTime(this.cache_.initTime);
25792    }
25793    /**
25794     * Normally gets the length in time of the video in seconds;
25795     * in all but the rarest use cases an argument will NOT be passed to the method
25796     *
25797     * > **NOTE**: The video must have started loading before the duration can be
25798     * known, and in the case of Flash, may not be known until the video starts
25799     * playing.
25800     *
25801     * @fires Player#durationchange
25802     *
25803     * @param {number} [seconds]
25804     *        The duration of the video to set in seconds
25805     *
25806     * @return {number}
25807     *         - The duration of the video in seconds when getting
25808     */
25809    ;
25810
25811    _proto.duration = function duration(seconds) {
25812      if (seconds === undefined) {
25813        // return NaN if the duration is not known
25814        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
25815      }
25816
25817      seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
25818
25819      if (seconds < 0) {
25820        seconds = Infinity;
25821      }
25822
25823      if (seconds !== this.cache_.duration) {
25824        // Cache the last set value for optimized scrubbing (esp. Flash)
25825        this.cache_.duration = seconds;
25826
25827        if (seconds === Infinity) {
25828          this.addClass('vjs-live');
25829        } else {
25830          this.removeClass('vjs-live');
25831        }
25832
25833        if (!isNaN(seconds)) {
25834          // Do not fire durationchange unless the duration value is known.
25835          // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
25836
25837          /**
25838           * @event Player#durationchange
25839           * @type {EventTarget~Event}
25840           */
25841          this.trigger('durationchange');
25842        }
25843      }
25844    }
25845    /**
25846     * Calculates how much time is left in the video. Not part
25847     * of the native video API.
25848     *
25849     * @return {number}
25850     *         The time remaining in seconds
25851     */
25852    ;
25853
25854    _proto.remainingTime = function remainingTime() {
25855      return this.duration() - this.currentTime();
25856    }
25857    /**
25858     * A remaining time function that is intented to be used when
25859     * the time is to be displayed directly to the user.
25860     *
25861     * @return {number}
25862     *         The rounded time remaining in seconds
25863     */
25864    ;
25865
25866    _proto.remainingTimeDisplay = function remainingTimeDisplay() {
25867      return Math.floor(this.duration()) - Math.floor(this.currentTime());
25868    } //
25869    // Kind of like an array of portions of the video that have been downloaded.
25870
25871    /**
25872     * Get a TimeRange object with an array of the times of the video
25873     * that have been downloaded. If you just want the percent of the
25874     * video that's been downloaded, use bufferedPercent.
25875     *
25876     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
25877     *
25878     * @return {TimeRange}
25879     *         A mock TimeRange object (following HTML spec)
25880     */
25881    ;
25882
25883    _proto.buffered = function buffered() {
25884      var buffered = this.techGet_('buffered');
25885
25886      if (!buffered || !buffered.length) {
25887        buffered = createTimeRanges(0, 0);
25888      }
25889
25890      return buffered;
25891    }
25892    /**
25893     * Get the percent (as a decimal) of the video that's been downloaded.
25894     * This method is not a part of the native HTML video API.
25895     *
25896     * @return {number}
25897     *         A decimal between 0 and 1 representing the percent
25898     *         that is buffered 0 being 0% and 1 being 100%
25899     */
25900    ;
25901
25902    _proto.bufferedPercent = function bufferedPercent$1() {
25903      return bufferedPercent(this.buffered(), this.duration());
25904    }
25905    /**
25906     * Get the ending time of the last buffered time range
25907     * This is used in the progress bar to encapsulate all time ranges.
25908     *
25909     * @return {number}
25910     *         The end of the last buffered time range
25911     */
25912    ;
25913
25914    _proto.bufferedEnd = function bufferedEnd() {
25915      var buffered = this.buffered();
25916      var duration = this.duration();
25917      var end = buffered.end(buffered.length - 1);
25918
25919      if (end > duration) {
25920        end = duration;
25921      }
25922
25923      return end;
25924    }
25925    /**
25926     * Get or set the current volume of the media
25927     *
25928     * @param  {number} [percentAsDecimal]
25929     *         The new volume as a decimal percent:
25930     *         - 0 is muted/0%/off
25931     *         - 1.0 is 100%/full
25932     *         - 0.5 is half volume or 50%
25933     *
25934     * @return {number}
25935     *         The current volume as a percent when getting
25936     */
25937    ;
25938
25939    _proto.volume = function volume(percentAsDecimal) {
25940      var vol;
25941
25942      if (percentAsDecimal !== undefined) {
25943        // Force value to between 0 and 1
25944        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
25945        this.cache_.volume = vol;
25946        this.techCall_('setVolume', vol);
25947
25948        if (vol > 0) {
25949          this.lastVolume_(vol);
25950        }
25951
25952        return;
25953      } // Default to 1 when returning current volume.
25954
25955
25956      vol = parseFloat(this.techGet_('volume'));
25957      return isNaN(vol) ? 1 : vol;
25958    }
25959    /**
25960     * Get the current muted state, or turn mute on or off
25961     *
25962     * @param {boolean} [muted]
25963     *        - true to mute
25964     *        - false to unmute
25965     *
25966     * @return {boolean}
25967     *         - true if mute is on and getting
25968     *         - false if mute is off and getting
25969     */
25970    ;
25971
25972    _proto.muted = function muted(_muted) {
25973      if (_muted !== undefined) {
25974        this.techCall_('setMuted', _muted);
25975        return;
25976      }
25977
25978      return this.techGet_('muted') || false;
25979    }
25980    /**
25981     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
25982     * indicates the state of muted on initial playback.
25983     *
25984     * ```js
25985     *   var myPlayer = videojs('some-player-id');
25986     *
25987     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
25988     *
25989     *   // get, should be false
25990     *   console.log(myPlayer.defaultMuted());
25991     *   // set to true
25992     *   myPlayer.defaultMuted(true);
25993     *   // get should be true
25994     *   console.log(myPlayer.defaultMuted());
25995     * ```
25996     *
25997     * @param {boolean} [defaultMuted]
25998     *        - true to mute
25999     *        - false to unmute
26000     *
26001     * @return {boolean|Player}
26002     *         - true if defaultMuted is on and getting
26003     *         - false if defaultMuted is off and getting
26004     *         - A reference to the current player when setting
26005     */
26006    ;
26007
26008    _proto.defaultMuted = function defaultMuted(_defaultMuted) {
26009      if (_defaultMuted !== undefined) {
26010        return this.techCall_('setDefaultMuted', _defaultMuted);
26011      }
26012
26013      return this.techGet_('defaultMuted') || false;
26014    }
26015    /**
26016     * Get the last volume, or set it
26017     *
26018     * @param  {number} [percentAsDecimal]
26019     *         The new last volume as a decimal percent:
26020     *         - 0 is muted/0%/off
26021     *         - 1.0 is 100%/full
26022     *         - 0.5 is half volume or 50%
26023     *
26024     * @return {number}
26025     *         the current value of lastVolume as a percent when getting
26026     *
26027     * @private
26028     */
26029    ;
26030
26031    _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
26032      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
26033        this.cache_.lastVolume = percentAsDecimal;
26034        return;
26035      }
26036
26037      return this.cache_.lastVolume;
26038    }
26039    /**
26040     * Check if current tech can support native fullscreen
26041     * (e.g. with built in controls like iOS, so not our flash swf)
26042     *
26043     * @return {boolean}
26044     *         if native fullscreen is supported
26045     */
26046    ;
26047
26048    _proto.supportsFullScreen = function supportsFullScreen() {
26049      return this.techGet_('supportsFullScreen') || false;
26050    }
26051    /**
26052     * Check if the player is in fullscreen mode or tell the player that it
26053     * is or is not in fullscreen mode.
26054     *
26055     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
26056     * property and instead document.fullscreenElement is used. But isFullscreen is
26057     * still a valuable property for internal player workings.
26058     *
26059     * @param  {boolean} [isFS]
26060     *         Set the players current fullscreen state
26061     *
26062     * @return {boolean}
26063     *         - true if fullscreen is on and getting
26064     *         - false if fullscreen is off and getting
26065     */
26066    ;
26067
26068    _proto.isFullscreen = function isFullscreen(isFS) {
26069      if (isFS !== undefined) {
26070        var oldValue = this.isFullscreen_;
26071        this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
26072        // this is the only place where we trigger fullscreenchange events for older browsers
26073        // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
26074
26075        if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
26076          /**
26077             * @event Player#fullscreenchange
26078             * @type {EventTarget~Event}
26079             */
26080          this.trigger('fullscreenchange');
26081        }
26082
26083        this.toggleFullscreenClass_();
26084        return;
26085      }
26086
26087      return this.isFullscreen_;
26088    }
26089    /**
26090     * Increase the size of the video to full screen
26091     * In some browsers, full screen is not supported natively, so it enters
26092     * "full window mode", where the video fills the browser window.
26093     * In browsers and devices that support native full screen, sometimes the
26094     * browser's default controls will be shown, and not the Video.js custom skin.
26095     * This includes most mobile devices (iOS, Android) and older versions of
26096     * Safari.
26097     *
26098     * @param  {Object} [fullscreenOptions]
26099     *         Override the player fullscreen options
26100     *
26101     * @fires Player#fullscreenchange
26102     */
26103    ;
26104
26105    _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) {
26106      var PromiseClass = this.options_.Promise || window$1.Promise;
26107
26108      if (PromiseClass) {
26109        var self = this;
26110        return new PromiseClass(function (resolve, reject) {
26111          function offHandler() {
26112            self.off('fullscreenerror', errorHandler);
26113            self.off('fullscreenchange', changeHandler);
26114          }
26115
26116          function changeHandler() {
26117            offHandler();
26118            resolve();
26119          }
26120
26121          function errorHandler(e, err) {
26122            offHandler();
26123            reject(err);
26124          }
26125
26126          self.one('fullscreenchange', changeHandler);
26127          self.one('fullscreenerror', errorHandler);
26128          var promise = self.requestFullscreenHelper_(fullscreenOptions);
26129
26130          if (promise) {
26131            promise.then(offHandler, offHandler);
26132            return promise;
26133          }
26134        });
26135      }
26136
26137      return this.requestFullscreenHelper_();
26138    };
26139
26140    _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) {
26141      var _this10 = this;
26142
26143      var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
26144      // Use defaults or player configured option unless passed directly to this method.
26145
26146      if (!this.fsApi_.prefixed) {
26147        fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
26148
26149        if (fullscreenOptions !== undefined) {
26150          fsOptions = fullscreenOptions;
26151        }
26152      } // This method works as follows:
26153      // 1. if a fullscreen api is available, use it
26154      //   1. call requestFullscreen with potential options
26155      //   2. if we got a promise from above, use it to update isFullscreen()
26156      // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
26157      //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
26158      // 3. otherwise, use "fullWindow" mode
26159
26160
26161      if (this.fsApi_.requestFullscreen) {
26162        var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
26163
26164        if (promise) {
26165          promise.then(function () {
26166            return _this10.isFullscreen(true);
26167          }, function () {
26168            return _this10.isFullscreen(false);
26169          });
26170        }
26171
26172        return promise;
26173      } else if (this.tech_.supportsFullScreen()) {
26174        // we can't take the video.js controls fullscreen but we can go fullscreen
26175        // with native controls
26176        this.techCall_('enterFullScreen');
26177      } else {
26178        // fullscreen isn't supported so we'll just stretch the video element to
26179        // fill the viewport
26180        this.enterFullWindow();
26181      }
26182    }
26183    /**
26184     * Return the video to its normal size after having been in full screen mode
26185     *
26186     * @fires Player#fullscreenchange
26187     */
26188    ;
26189
26190    _proto.exitFullscreen = function exitFullscreen() {
26191      var PromiseClass = this.options_.Promise || window$1.Promise;
26192
26193      if (PromiseClass) {
26194        var self = this;
26195        return new PromiseClass(function (resolve, reject) {
26196          function offHandler() {
26197            self.off('fullscreenerror', errorHandler);
26198            self.off('fullscreenchange', changeHandler);
26199          }
26200
26201          function changeHandler() {
26202            offHandler();
26203            resolve();
26204          }
26205
26206          function errorHandler(e, err) {
26207            offHandler();
26208            reject(err);
26209          }
26210
26211          self.one('fullscreenchange', changeHandler);
26212          self.one('fullscreenerror', errorHandler);
26213          var promise = self.exitFullscreenHelper_();
26214
26215          if (promise) {
26216            promise.then(offHandler, offHandler);
26217            return promise;
26218          }
26219        });
26220      }
26221
26222      return this.exitFullscreenHelper_();
26223    };
26224
26225    _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() {
26226      var _this11 = this;
26227
26228      if (this.fsApi_.requestFullscreen) {
26229        var promise = document[this.fsApi_.exitFullscreen]();
26230
26231        if (promise) {
26232          promise.then(function () {
26233            return _this11.isFullscreen(false);
26234          });
26235        }
26236
26237        return promise;
26238      } else if (this.tech_.supportsFullScreen()) {
26239        this.techCall_('exitFullScreen');
26240      } else {
26241        this.exitFullWindow();
26242      }
26243    }
26244    /**
26245     * When fullscreen isn't supported we can stretch the
26246     * video container to as wide as the browser will let us.
26247     *
26248     * @fires Player#enterFullWindow
26249     */
26250    ;
26251
26252    _proto.enterFullWindow = function enterFullWindow() {
26253      this.isFullscreen(true);
26254      this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
26255
26256      this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
26257
26258      on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
26259
26260      document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
26261
26262      addClass(document.body, 'vjs-full-window');
26263      /**
26264       * @event Player#enterFullWindow
26265       * @type {EventTarget~Event}
26266       */
26267
26268      this.trigger('enterFullWindow');
26269    }
26270    /**
26271     * Check for call to either exit full window or
26272     * full screen on ESC key
26273     *
26274     * @param {string} event
26275     *        Event to check for key press
26276     */
26277    ;
26278
26279    _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
26280      if (keycode.isEventKey(event, 'Esc')) {
26281        if (this.isFullscreen() === true) {
26282          this.exitFullscreen();
26283        } else {
26284          this.exitFullWindow();
26285        }
26286      }
26287    }
26288    /**
26289     * Exit full window
26290     *
26291     * @fires Player#exitFullWindow
26292     */
26293    ;
26294
26295    _proto.exitFullWindow = function exitFullWindow() {
26296      this.isFullscreen(false);
26297      this.isFullWindow = false;
vendor: 9,567 bytes, lines 26298-26576
26298      off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
26299
26300      document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
26301
26302      removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
26303      // this.positionAll();
26304
26305      /**
26306       * @event Player#exitFullWindow
26307       * @type {EventTarget~Event}
26308       */
26309
26310      this.trigger('exitFullWindow');
26311    }
26312    /**
26313     * Disable Picture-in-Picture mode.
26314     *
26315     * @param {boolean} value
26316     *                  - true will disable Picture-in-Picture mode
26317     *                  - false will enable Picture-in-Picture mode
26318     */
26319    ;
26320
26321    _proto.disablePictureInPicture = function disablePictureInPicture(value) {
26322      if (value === undefined) {
26323        return this.techGet_('disablePictureInPicture');
26324      }
26325
26326      this.techCall_('setDisablePictureInPicture', value);
26327      this.options_.disablePictureInPicture = value;
26328      this.trigger('disablepictureinpicturechanged');
26329    }
26330    /**
26331     * Check if the player is in Picture-in-Picture mode or tell the player that it
26332     * is or is not in Picture-in-Picture mode.
26333     *
26334     * @param  {boolean} [isPiP]
26335     *         Set the players current Picture-in-Picture state
26336     *
26337     * @return {boolean}
26338     *         - true if Picture-in-Picture is on and getting
26339     *         - false if Picture-in-Picture is off and getting
26340     */
26341    ;
26342
26343    _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) {
26344      if (isPiP !== undefined) {
26345        this.isInPictureInPicture_ = !!isPiP;
26346        this.togglePictureInPictureClass_();
26347        return;
26348      }
26349
26350      return !!this.isInPictureInPicture_;
26351    }
26352    /**
26353     * Create a floating video window always on top of other windows so that users may
26354     * continue consuming media while they interact with other content sites, or
26355     * applications on their device.
26356     *
26357     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
26358     *
26359     * @fires Player#enterpictureinpicture
26360     *
26361     * @return {Promise}
26362     *         A promise with a Picture-in-Picture window.
26363     */
26364    ;
26365
26366    _proto.requestPictureInPicture = function requestPictureInPicture() {
26367      if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
26368        /**
26369         * This event fires when the player enters picture in picture mode
26370         *
26371         * @event Player#enterpictureinpicture
26372         * @type {EventTarget~Event}
26373         */
26374        return this.techGet_('requestPictureInPicture');
26375      }
26376    }
26377    /**
26378     * Exit Picture-in-Picture mode.
26379     *
26380     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
26381     *
26382     * @fires Player#leavepictureinpicture
26383     *
26384     * @return {Promise}
26385     *         A promise.
26386     */
26387    ;
26388
26389    _proto.exitPictureInPicture = function exitPictureInPicture() {
26390      if ('pictureInPictureEnabled' in document) {
26391        /**
26392         * This event fires when the player leaves picture in picture mode
26393         *
26394         * @event Player#leavepictureinpicture
26395         * @type {EventTarget~Event}
26396         */
26397        return document.exitPictureInPicture();
26398      }
26399    }
26400    /**
26401     * Called when this Player has focus and a key gets pressed down, or when
26402     * any Component of this player receives a key press that it doesn't handle.
26403     * This allows player-wide hotkeys (either as defined below, or optionally
26404     * by an external function).
26405     *
26406     * @param {EventTarget~Event} event
26407     *        The `keydown` event that caused this function to be called.
26408     *
26409     * @listens keydown
26410     */
26411    ;
26412
26413    _proto.handleKeyDown = function handleKeyDown(event) {
26414      var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
26415
26416      if (!userActions || !userActions.hotkeys) {
26417        return;
26418      } // Function that determines whether or not to exclude an element from
26419      // hotkeys handling.
26420
26421
26422      var excludeElement = function excludeElement(el) {
26423        var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements.
26424
26425        if (el.isContentEditable) {
26426          return true;
26427        } // Inputs matching these types will still trigger hotkey handling as
26428        // they are not text inputs.
26429
26430
26431        var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
26432
26433        if (tagName === 'input') {
26434          return allowedInputTypes.indexOf(el.type) === -1;
26435        } // The final test is by tag name. These tags will be excluded entirely.
26436
26437
26438        var excludedTags = ['textarea'];
26439        return excludedTags.indexOf(tagName) !== -1;
26440      }; // Bail out if the user is focused on an interactive form element.
26441
26442
26443      if (excludeElement(this.el_.ownerDocument.activeElement)) {
26444        return;
26445      }
26446
26447      if (typeof userActions.hotkeys === 'function') {
26448        userActions.hotkeys.call(this, event);
26449      } else {
26450        this.handleHotkeys(event);
26451      }
26452    }
26453    /**
26454     * Called when this Player receives a hotkey keydown event.
26455     * Supported player-wide hotkeys are:
26456     *
26457     *   f          - toggle fullscreen
26458     *   m          - toggle mute
26459     *   k or Space - toggle play/pause
26460     *
26461     * @param {EventTarget~Event} event
26462     *        The `keydown` event that caused this function to be called.
26463     */
26464    ;
26465
26466    _proto.handleHotkeys = function handleHotkeys(event) {
26467      var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
26468
26469      var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
26470          fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
26471        return keycode.isEventKey(keydownEvent, 'f');
26472      } : _hotkeys$fullscreenKe,
26473          _hotkeys$muteKey = hotkeys.muteKey,
26474          muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
26475        return keycode.isEventKey(keydownEvent, 'm');
26476      } : _hotkeys$muteKey,
26477          _hotkeys$playPauseKey = hotkeys.playPauseKey,
26478          playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
26479        return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
26480      } : _hotkeys$playPauseKey;
26481
26482      if (fullscreenKey.call(this, event)) {
26483        event.preventDefault();
26484        event.stopPropagation();
26485        var FSToggle = Component.getComponent('FullscreenToggle');
26486
26487        if (document[this.fsApi_.fullscreenEnabled] !== false) {
26488          FSToggle.prototype.handleClick.call(this, event);
26489        }
26490      } else if (muteKey.call(this, event)) {
26491        event.preventDefault();
26492        event.stopPropagation();
26493        var MuteToggle = Component.getComponent('MuteToggle');
26494        MuteToggle.prototype.handleClick.call(this, event);
26495      } else if (playPauseKey.call(this, event)) {
26496        event.preventDefault();
26497        event.stopPropagation();
26498        var PlayToggle = Component.getComponent('PlayToggle');
26499        PlayToggle.prototype.handleClick.call(this, event);
26500      }
26501    }
26502    /**
26503     * Check whether the player can play a given mimetype
26504     *
26505     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
26506     *
26507     * @param {string} type
26508     *        The mimetype to check
26509     *
26510     * @return {string}
26511     *         'probably', 'maybe', or '' (empty string)
26512     */
26513    ;
26514
26515    _proto.canPlayType = function canPlayType(type) {
26516      var can; // Loop through each playback technology in the options order
26517
26518      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
26519        var techName = j[i];
26520        var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
26521        // Remove once that deprecated behavior is removed.
26522
26523        if (!tech) {
26524          tech = Component.getComponent(techName);
26525        } // Check if the current tech is defined before continuing
26526
26527
26528        if (!tech) {
26529          log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
26530          continue;
26531        } // Check if the browser supports this technology
26532
26533
26534        if (tech.isSupported()) {
26535          can = tech.canPlayType(type);
26536
26537          if (can) {
26538            return can;
26539          }
26540        }
26541      }
26542
26543      return '';
26544    }
26545    /**
26546     * Select source based on tech-order or source-order
26547     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
26548     * defaults to tech-order selection
26549     *
26550     * @param {Array} sources
26551     *        The sources for a media asset
26552     *
26553     * @return {Object|boolean}
26554     *         Object of source and tech order or false
26555     */
26556    ;
26557
26558    _proto.selectSource = function selectSource(sources) {
26559      var _this12 = this;
26560
26561      // Get only the techs specified in `techOrder` that exist and are supported by the
26562      // current platform
26563      var techs = this.options_.techOrder.map(function (techName) {
26564        return [techName, Tech.getTech(techName)];
26565      }).filter(function (_ref) {
26566        var techName = _ref[0],
26567            tech = _ref[1];
26568
26569        // Check if the current tech is defined before continuing
26570        if (tech) {
26571          // Check if the browser supports this technology
26572          return tech.isSupported();
26573        }
26574
26575        log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
26576        return false;
vendor: 5,209 bytes, lines 26577-26736
26577      }); // Iterate over each `innerArray` element once per `outerArray` element and execute
26578      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
26579      // that value.
26580
26581      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
26582        var found;
26583        outerArray.some(function (outerChoice) {
26584          return innerArray.some(function (innerChoice) {
26585            found = tester(outerChoice, innerChoice);
26586
26587            if (found) {
26588              return true;
26589            }
26590          });
26591        });
26592        return found;
26593      };
26594
26595      var foundSourceAndTech;
26596
26597      var flip = function flip(fn) {
26598        return function (a, b) {
26599          return fn(b, a);
26600        };
26601      };
26602
26603      var finder = function finder(_ref2, source) {
26604        var techName = _ref2[0],
26605            tech = _ref2[1];
26606
26607        if (tech.canPlaySource(source, _this12.options_[techName.toLowerCase()])) {
26608          return {
26609            source: source,
26610            tech: techName
26611          };
26612        }
26613      }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
26614      // to select from them based on their priority.
26615
26616
26617      if (this.options_.sourceOrder) {
26618        // Source-first ordering
26619        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
26620      } else {
26621        // Tech-first ordering
26622        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
26623      }
26624
26625      return foundSourceAndTech || false;
26626    }
26627    /**
26628     * Get or set the video source.
26629     *
26630     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
26631     *        A SourceObject, an array of SourceObjects, or a string referencing
26632     *        a URL to a media source. It is _highly recommended_ that an object
26633     *        or array of objects is used here, so that source selection
26634     *        algorithms can take the `type` into account.
26635     *
26636     *        If not provided, this method acts as a getter.
26637     *
26638     * @return {string|undefined}
26639     *         If the `source` argument is missing, returns the current source
26640     *         URL. Otherwise, returns nothing/undefined.
26641     */
26642    ;
26643
26644    _proto.src = function src(source) {
26645      var _this13 = this;
26646
26647      // getter usage
26648      if (typeof source === 'undefined') {
26649        return this.cache_.src || '';
26650      } // filter out invalid sources and turn our source into
26651      // an array of source objects
26652
26653
26654      var sources = filterSource(source); // if a source was passed in then it is invalid because
26655      // it was filtered to a zero length Array. So we have to
26656      // show an error
26657
26658      if (!sources.length) {
26659        this.setTimeout(function () {
26660          this.error({
26661            code: 4,
26662            message: this.localize(this.options_.notSupportedMessage)
26663          });
26664        }, 0);
26665        return;
26666      } // initial sources
26667
26668
26669      this.changingSrc_ = true;
26670      this.cache_.sources = sources;
26671      this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
26672
26673      setSource(this, sources[0], function (middlewareSource, mws) {
26674        _this13.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
26675        // the source that is selected could be out of order from the cache update above this callback.
26676
26677        _this13.cache_.sources = sources;
26678
26679        _this13.updateSourceCaches_(middlewareSource);
26680
26681        var err = _this13.src_(middlewareSource);
26682
26683        if (err) {
26684          if (sources.length > 1) {
26685            return _this13.src(sources.slice(1));
26686          }
26687
26688          _this13.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
26689
26690          _this13.setTimeout(function () {
26691            this.error({
26692              code: 4,
26693              message: this.localize(this.options_.notSupportedMessage)
26694            });
26695          }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
26696          // this needs a better comment about why this is needed
26697
26698
26699          _this13.triggerReady();
26700
26701          return;
26702        }
26703
26704        setTech(mws, _this13.tech_);
26705      });
26706    }
26707    /**
26708     * Set the source object on the tech, returns a boolean that indicates whether
26709     * there is a tech that can play the source or not
26710     *
26711     * @param {Tech~SourceObject} source
26712     *        The source object to set on the Tech
26713     *
26714     * @return {boolean}
26715     *         - True if there is no Tech to playback this source
26716     *         - False otherwise
26717     *
26718     * @private
26719     */
26720    ;
26721
26722    _proto.src_ = function src_(source) {
26723      var _this14 = this;
26724
26725      var sourceTech = this.selectSource([source]);
26726
26727      if (!sourceTech) {
26728        return true;
26729      }
26730
26731      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
26732        this.changingSrc_ = true; // load this technology with the chosen source
26733
26734        this.loadTech_(sourceTech.tech, sourceTech.source);
26735        this.tech_.ready(function () {
26736          _this14.changingSrc_ = false;
vendor: 47,890 bytes, lines 26737-28291
26737        });
26738        return false;
26739      } // wait until the tech is ready to set the source
26740      // and set it synchronously if possible (#2326)
26741
26742
26743      this.ready(function () {
26744        // The setSource tech method was added with source handlers
26745        // so older techs won't support it
26746        // We need to check the direct prototype for the case where subclasses
26747        // of the tech do not support source handlers
26748        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
26749          this.techCall_('setSource', source);
26750        } else {
26751          this.techCall_('src', source.src);
26752        }
26753
26754        this.changingSrc_ = false;
26755      }, true);
26756      return false;
26757    }
26758    /**
26759     * Begin loading the src data.
26760     */
26761    ;
26762
26763    _proto.load = function load() {
26764      this.techCall_('load');
26765    }
26766    /**
26767     * Reset the player. Loads the first tech in the techOrder,
26768     * removes all the text tracks in the existing `tech`,
26769     * and calls `reset` on the `tech`.
26770     */
26771    ;
26772
26773    _proto.reset = function reset() {
26774      var _this15 = this;
26775
26776      var PromiseClass = this.options_.Promise || window$1.Promise;
26777
26778      if (this.paused() || !PromiseClass) {
26779        this.doReset_();
26780      } else {
26781        var playPromise = this.play();
26782        silencePromise(playPromise.then(function () {
26783          return _this15.doReset_();
26784        }));
26785      }
26786    };
26787
26788    _proto.doReset_ = function doReset_() {
26789      if (this.tech_) {
26790        this.tech_.clearTracks('text');
26791      }
26792
26793      this.resetCache_();
26794      this.poster('');
26795      this.loadTech_(this.options_.techOrder[0], null);
26796      this.techCall_('reset');
26797      this.resetControlBarUI_();
26798
26799      if (isEvented(this)) {
26800        this.trigger('playerreset');
26801      }
26802    }
26803    /**
26804     * Reset Control Bar's UI by calling sub-methods that reset
26805     * all of Control Bar's components
26806     */
26807    ;
26808
26809    _proto.resetControlBarUI_ = function resetControlBarUI_() {
26810      this.resetProgressBar_();
26811      this.resetPlaybackRate_();
26812      this.resetVolumeBar_();
26813    }
26814    /**
26815     * Reset tech's progress so progress bar is reset in the UI
26816     */
26817    ;
26818
26819    _proto.resetProgressBar_ = function resetProgressBar_() {
26820      this.currentTime(0);
26821      var _this$controlBar = this.controlBar,
26822          durationDisplay = _this$controlBar.durationDisplay,
26823          remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
26824
26825      if (durationDisplay) {
26826        durationDisplay.updateContent();
26827      }
26828
26829      if (remainingTimeDisplay) {
26830        remainingTimeDisplay.updateContent();
26831      }
26832    }
26833    /**
26834     * Reset Playback ratio
26835     */
26836    ;
26837
26838    _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
26839      this.playbackRate(this.defaultPlaybackRate());
26840      this.handleTechRateChange_();
26841    }
26842    /**
26843     * Reset Volume bar
26844     */
26845    ;
26846
26847    _proto.resetVolumeBar_ = function resetVolumeBar_() {
26848      this.volume(1.0);
26849      this.trigger('volumechange');
26850    }
26851    /**
26852     * Returns all of the current source objects.
26853     *
26854     * @return {Tech~SourceObject[]}
26855     *         The current source objects
26856     */
26857    ;
26858
26859    _proto.currentSources = function currentSources() {
26860      var source = this.currentSource();
26861      var sources = []; // assume `{}` or `{ src }`
26862
26863      if (Object.keys(source).length !== 0) {
26864        sources.push(source);
26865      }
26866
26867      return this.cache_.sources || sources;
26868    }
26869    /**
26870     * Returns the current source object.
26871     *
26872     * @return {Tech~SourceObject}
26873     *         The current source object
26874     */
26875    ;
26876
26877    _proto.currentSource = function currentSource() {
26878      return this.cache_.source || {};
26879    }
26880    /**
26881     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
26882     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
26883     *
26884     * @return {string}
26885     *         The current source
26886     */
26887    ;
26888
26889    _proto.currentSrc = function currentSrc() {
26890      return this.currentSource() && this.currentSource().src || '';
26891    }
26892    /**
26893     * Get the current source type e.g. video/mp4
26894     * This can allow you rebuild the current source object so that you could load the same
26895     * source and tech later
26896     *
26897     * @return {string}
26898     *         The source MIME type
26899     */
26900    ;
26901
26902    _proto.currentType = function currentType() {
26903      return this.currentSource() && this.currentSource().type || '';
26904    }
26905    /**
26906     * Get or set the preload attribute
26907     *
26908     * @param {boolean} [value]
26909     *        - true means that we should preload
26910     *        - false means that we should not preload
26911     *
26912     * @return {string}
26913     *         The preload attribute value when getting
26914     */
26915    ;
26916
26917    _proto.preload = function preload(value) {
26918      if (value !== undefined) {
26919        this.techCall_('setPreload', value);
26920        this.options_.preload = value;
26921        return;
26922      }
26923
26924      return this.techGet_('preload');
26925    }
26926    /**
26927     * Get or set the autoplay option. When this is a boolean it will
26928     * modify the attribute on the tech. When this is a string the attribute on
26929     * the tech will be removed and `Player` will handle autoplay on loadstarts.
26930     *
26931     * @param {boolean|string} [value]
26932     *        - true: autoplay using the browser behavior
26933     *        - false: do not autoplay
26934     *        - 'play': call play() on every loadstart
26935     *        - 'muted': call muted() then play() on every loadstart
26936     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
26937     *        - *: values other than those listed here will be set `autoplay` to true
26938     *
26939     * @return {boolean|string}
26940     *         The current value of autoplay when getting
26941     */
26942    ;
26943
26944    _proto.autoplay = function autoplay(value) {
26945      // getter usage
26946      if (value === undefined) {
26947        return this.options_.autoplay || false;
26948      }
26949
26950      var techAutoplay; // if the value is a valid string set it to that
26951
26952      if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
26953        this.options_.autoplay = value;
26954        this.manualAutoplay_(value);
26955        techAutoplay = false; // any falsy value sets autoplay to false in the browser,
26956        // lets do the same
26957      } else if (!value) {
26958        this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
26959      } else {
26960        this.options_.autoplay = true;
26961      }
26962
26963      techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
26964      // a setAutoplay call on tech ready. We do this because the
26965      // autoplay option will be passed in the constructor and we
26966      // do not need to set it twice
26967
26968      if (this.tech_) {
26969        this.techCall_('setAutoplay', techAutoplay);
26970      }
26971    }
26972    /**
26973     * Set or unset the playsinline attribute.
26974     * Playsinline tells the browser that non-fullscreen playback is preferred.
26975     *
26976     * @param {boolean} [value]
26977     *        - true means that we should try to play inline by default
26978     *        - false means that we should use the browser's default playback mode,
26979     *          which in most cases is inline. iOS Safari is a notable exception
26980     *          and plays fullscreen by default.
26981     *
26982     * @return {string|Player}
26983     *         - the current value of playsinline
26984     *         - the player when setting
26985     *
26986     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
26987     */
26988    ;
26989
26990    _proto.playsinline = function playsinline(value) {
26991      if (value !== undefined) {
26992        this.techCall_('setPlaysinline', value);
26993        this.options_.playsinline = value;
26994        return this;
26995      }
26996
26997      return this.techGet_('playsinline');
26998    }
26999    /**
27000     * Get or set the loop attribute on the video element.
27001     *
27002     * @param {boolean} [value]
27003     *        - true means that we should loop the video
27004     *        - false means that we should not loop the video
27005     *
27006     * @return {boolean}
27007     *         The current value of loop when getting
27008     */
27009    ;
27010
27011    _proto.loop = function loop(value) {
27012      if (value !== undefined) {
27013        this.techCall_('setLoop', value);
27014        this.options_.loop = value;
27015        return;
27016      }
27017
27018      return this.techGet_('loop');
27019    }
27020    /**
27021     * Get or set the poster image source url
27022     *
27023     * @fires Player#posterchange
27024     *
27025     * @param {string} [src]
27026     *        Poster image source URL
27027     *
27028     * @return {string}
27029     *         The current value of poster when getting
27030     */
27031    ;
27032
27033    _proto.poster = function poster(src) {
27034      if (src === undefined) {
27035        return this.poster_;
27036      } // The correct way to remove a poster is to set as an empty string
27037      // other falsey values will throw errors
27038
27039
27040      if (!src) {
27041        src = '';
27042      }
27043
27044      if (src === this.poster_) {
27045        return;
27046      } // update the internal poster variable
27047
27048
27049      this.poster_ = src; // update the tech's poster
27050
27051      this.techCall_('setPoster', src);
27052      this.isPosterFromTech_ = false; // alert components that the poster has been set
27053
27054      /**
27055       * This event fires when the poster image is changed on the player.
27056       *
27057       * @event Player#posterchange
27058       * @type {EventTarget~Event}
27059       */
27060
27061      this.trigger('posterchange');
27062    }
27063    /**
27064     * Some techs (e.g. YouTube) can provide a poster source in an
27065     * asynchronous way. We want the poster component to use this
27066     * poster source so that it covers up the tech's controls.
27067     * (YouTube's play button). However we only want to use this
27068     * source if the player user hasn't set a poster through
27069     * the normal APIs.
27070     *
27071     * @fires Player#posterchange
27072     * @listens Tech#posterchange
27073     * @private
27074     */
27075    ;
27076
27077    _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
27078      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
27079        var newPoster = this.tech_.poster() || '';
27080
27081        if (newPoster !== this.poster_) {
27082          this.poster_ = newPoster;
27083          this.isPosterFromTech_ = true; // Let components know the poster has changed
27084
27085          this.trigger('posterchange');
27086        }
27087      }
27088    }
27089    /**
27090     * Get or set whether or not the controls are showing.
27091     *
27092     * @fires Player#controlsenabled
27093     *
27094     * @param {boolean} [bool]
27095     *        - true to turn controls on
27096     *        - false to turn controls off
27097     *
27098     * @return {boolean}
27099     *         The current value of controls when getting
27100     */
27101    ;
27102
27103    _proto.controls = function controls(bool) {
27104      if (bool === undefined) {
27105        return !!this.controls_;
27106      }
27107
27108      bool = !!bool; // Don't trigger a change event unless it actually changed
27109
27110      if (this.controls_ === bool) {
27111        return;
27112      }
27113
27114      this.controls_ = bool;
27115
27116      if (this.usingNativeControls()) {
27117        this.techCall_('setControls', bool);
27118      }
27119
27120      if (this.controls_) {
27121        this.removeClass('vjs-controls-disabled');
27122        this.addClass('vjs-controls-enabled');
27123        /**
27124         * @event Player#controlsenabled
27125         * @type {EventTarget~Event}
27126         */
27127
27128        this.trigger('controlsenabled');
27129
27130        if (!this.usingNativeControls()) {
27131          this.addTechControlsListeners_();
27132        }
27133      } else {
27134        this.removeClass('vjs-controls-enabled');
27135        this.addClass('vjs-controls-disabled');
27136        /**
27137         * @event Player#controlsdisabled
27138         * @type {EventTarget~Event}
27139         */
27140
27141        this.trigger('controlsdisabled');
27142
27143        if (!this.usingNativeControls()) {
27144          this.removeTechControlsListeners_();
27145        }
27146      }
27147    }
27148    /**
27149     * Toggle native controls on/off. Native controls are the controls built into
27150     * devices (e.g. default iPhone controls), Flash, or other techs
27151     * (e.g. Vimeo Controls)
27152     * **This should only be set by the current tech, because only the tech knows
27153     * if it can support native controls**
27154     *
27155     * @fires Player#usingnativecontrols
27156     * @fires Player#usingcustomcontrols
27157     *
27158     * @param {boolean} [bool]
27159     *        - true to turn native controls on
27160     *        - false to turn native controls off
27161     *
27162     * @return {boolean}
27163     *         The current value of native controls when getting
27164     */
27165    ;
27166
27167    _proto.usingNativeControls = function usingNativeControls(bool) {
27168      if (bool === undefined) {
27169        return !!this.usingNativeControls_;
27170      }
27171
27172      bool = !!bool; // Don't trigger a change event unless it actually changed
27173
27174      if (this.usingNativeControls_ === bool) {
27175        return;
27176      }
27177
27178      this.usingNativeControls_ = bool;
27179
27180      if (this.usingNativeControls_) {
27181        this.addClass('vjs-using-native-controls');
27182        /**
27183         * player is using the native device controls
27184         *
27185         * @event Player#usingnativecontrols
27186         * @type {EventTarget~Event}
27187         */
27188
27189        this.trigger('usingnativecontrols');
27190      } else {
27191        this.removeClass('vjs-using-native-controls');
27192        /**
27193         * player is using the custom HTML controls
27194         *
27195         * @event Player#usingcustomcontrols
27196         * @type {EventTarget~Event}
27197         */
27198
27199        this.trigger('usingcustomcontrols');
27200      }
27201    }
27202    /**
27203     * Set or get the current MediaError
27204     *
27205     * @fires Player#error
27206     *
27207     * @param  {MediaError|string|number} [err]
27208     *         A MediaError or a string/number to be turned
27209     *         into a MediaError
27210     *
27211     * @return {MediaError|null}
27212     *         The current MediaError when getting (or null)
27213     */
27214    ;
27215
27216    _proto.error = function error(err) {
27217      if (err === undefined) {
27218        return this.error_ || null;
27219      } // Suppress the first error message for no compatible source until
27220      // user interaction
27221
27222
27223      if (this.options_.suppressNotSupportedError && err && err.code === 4) {
27224        var triggerSuppressedError = function triggerSuppressedError() {
27225          this.error(err);
27226        };
27227
27228        this.options_.suppressNotSupportedError = false;
27229        this.any(['click', 'touchstart'], triggerSuppressedError);
27230        this.one('loadstart', function () {
27231          this.off(['click', 'touchstart'], triggerSuppressedError);
27232        });
27233        return;
27234      } // restoring to default
27235
27236
27237      if (err === null) {
27238        this.error_ = err;
27239        this.removeClass('vjs-error');
27240
27241        if (this.errorDisplay) {
27242          this.errorDisplay.close();
27243        }
27244
27245        return;
27246      }
27247
27248      this.error_ = new MediaError(err); // add the vjs-error classname to the player
27249
27250      this.addClass('vjs-error'); // log the name of the error type and any message
27251      // IE11 logs "[object object]" and required you to expand message to see error object
27252
27253      log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
27254      /**
27255       * @event Player#error
27256       * @type {EventTarget~Event}
27257       */
27258
27259      this.trigger('error');
27260      return;
27261    }
27262    /**
27263     * Report user activity
27264     *
27265     * @param {Object} event
27266     *        Event object
27267     */
27268    ;
27269
27270    _proto.reportUserActivity = function reportUserActivity(event) {
27271      this.userActivity_ = true;
27272    }
27273    /**
27274     * Get/set if user is active
27275     *
27276     * @fires Player#useractive
27277     * @fires Player#userinactive
27278     *
27279     * @param {boolean} [bool]
27280     *        - true if the user is active
27281     *        - false if the user is inactive
27282     *
27283     * @return {boolean}
27284     *         The current value of userActive when getting
27285     */
27286    ;
27287
27288    _proto.userActive = function userActive(bool) {
27289      if (bool === undefined) {
27290        return this.userActive_;
27291      }
27292
27293      bool = !!bool;
27294
27295      if (bool === this.userActive_) {
27296        return;
27297      }
27298
27299      this.userActive_ = bool;
27300
27301      if (this.userActive_) {
27302        this.userActivity_ = true;
27303        this.removeClass('vjs-user-inactive');
27304        this.addClass('vjs-user-active');
27305        /**
27306         * @event Player#useractive
27307         * @type {EventTarget~Event}
27308         */
27309
27310        this.trigger('useractive');
27311        return;
27312      } // Chrome/Safari/IE have bugs where when you change the cursor it can
27313      // trigger a mousemove event. This causes an issue when you're hiding
27314      // the cursor when the user is inactive, and a mousemove signals user
27315      // activity. Making it impossible to go into inactive mode. Specifically
27316      // this happens in fullscreen when we really need to hide the cursor.
27317      //
27318      // When this gets resolved in ALL browsers it can be removed
27319      // https://code.google.com/p/chromium/issues/detail?id=103041
27320
27321
27322      if (this.tech_) {
27323        this.tech_.one('mousemove', function (e) {
27324          e.stopPropagation();
27325          e.preventDefault();
27326        });
27327      }
27328
27329      this.userActivity_ = false;
27330      this.removeClass('vjs-user-active');
27331      this.addClass('vjs-user-inactive');
27332      /**
27333       * @event Player#userinactive
27334       * @type {EventTarget~Event}
27335       */
27336
27337      this.trigger('userinactive');
27338    }
27339    /**
27340     * Listen for user activity based on timeout value
27341     *
27342     * @private
27343     */
27344    ;
27345
27346    _proto.listenForUserActivity_ = function listenForUserActivity_() {
27347      var mouseInProgress;
27348      var lastMoveX;
27349      var lastMoveY;
27350      var handleActivity = bind(this, this.reportUserActivity);
27351
27352      var handleMouseMove = function handleMouseMove(e) {
27353        // #1068 - Prevent mousemove spamming
27354        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
27355        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
27356          lastMoveX = e.screenX;
27357          lastMoveY = e.screenY;
27358          handleActivity();
27359        }
27360      };
27361
27362      var handleMouseDown = function handleMouseDown() {
27363        handleActivity(); // For as long as the they are touching the device or have their mouse down,
27364        // we consider them active even if they're not moving their finger or mouse.
27365        // So we want to continue to update that they are active
27366
27367        this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
27368        // as the activityCheck interval (250) should ensure we never miss the
27369        // next activityCheck
27370
27371        mouseInProgress = this.setInterval(handleActivity, 250);
27372      };
27373
27374      var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) {
27375        handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
27376
27377        this.clearInterval(mouseInProgress);
27378      }; // Any mouse movement will be considered user activity
27379
27380
27381      this.on('mousedown', handleMouseDown);
27382      this.on('mousemove', handleMouseMove);
27383      this.on('mouseup', handleMouseUpAndMouseLeave);
27384      this.on('mouseleave', handleMouseUpAndMouseLeave);
27385      var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
27386      // controlBar would no longer be hidden by default timeout.
27387
27388      if (controlBar && !IS_IOS && !IS_ANDROID) {
27389        controlBar.on('mouseenter', function (event) {
27390          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
27391          this.player().options_.inactivityTimeout = 0;
27392        });
27393        controlBar.on('mouseleave', function (event) {
27394          this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
27395        });
27396      } // Listen for keyboard navigation
27397      // Shouldn't need to use inProgress interval because of key repeat
27398
27399
27400      this.on('keydown', handleActivity);
27401      this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
27402      // the mousemove/touchmove function itself, to prevent performance degradation.
27403      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
27404      // then gets picked up by this loop
27405      // http://ejohn.org/blog/learning-from-twitter/
27406
27407      var inactivityTimeout;
27408      this.setInterval(function () {
27409        // Check to see if mouse/touch activity has happened
27410        if (!this.userActivity_) {
27411          return;
27412        } // Reset the activity tracker
27413
27414
27415        this.userActivity_ = false; // If the user state was inactive, set the state to active
27416
27417        this.userActive(true); // Clear any existing inactivity timeout to start the timer over
27418
27419        this.clearTimeout(inactivityTimeout);
27420        var timeout = this.options_.inactivityTimeout;
27421
27422        if (timeout <= 0) {
27423          return;
27424        } // In <timeout> milliseconds, if no more activity has occurred the
27425        // user will be considered inactive
27426
27427
27428        inactivityTimeout = this.setTimeout(function () {
27429          // Protect against the case where the inactivityTimeout can trigger just
27430          // before the next user activity is picked up by the activity check loop
27431          // causing a flicker
27432          if (!this.userActivity_) {
27433            this.userActive(false);
27434          }
27435        }, timeout);
27436      }, 250);
27437    }
27438    /**
27439     * Gets or sets the current playback rate. A playback rate of
27440     * 1.0 represents normal speed and 0.5 would indicate half-speed
27441     * playback, for instance.
27442     *
27443     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
27444     *
27445     * @param {number} [rate]
27446     *       New playback rate to set.
27447     *
27448     * @return {number}
27449     *         The current playback rate when getting or 1.0
27450     */
27451    ;
27452
27453    _proto.playbackRate = function playbackRate(rate) {
27454      if (rate !== undefined) {
27455        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
27456        // that is registered above
27457        this.techCall_('setPlaybackRate', rate);
27458        return;
27459      }
27460
27461      if (this.tech_ && this.tech_.featuresPlaybackRate) {
27462        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
27463      }
27464
27465      return 1.0;
27466    }
27467    /**
27468     * Gets or sets the current default playback rate. A default playback rate of
27469     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
27470     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
27471     * not the current playbackRate.
27472     *
27473     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
27474     *
27475     * @param {number} [rate]
27476     *       New default playback rate to set.
27477     *
27478     * @return {number|Player}
27479     *         - The default playback rate when getting or 1.0
27480     *         - the player when setting
27481     */
27482    ;
27483
27484    _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
27485      if (rate !== undefined) {
27486        return this.techCall_('setDefaultPlaybackRate', rate);
27487      }
27488
27489      if (this.tech_ && this.tech_.featuresPlaybackRate) {
27490        return this.techGet_('defaultPlaybackRate');
27491      }
27492
27493      return 1.0;
27494    }
27495    /**
27496     * Gets or sets the audio flag
27497     *
27498     * @param {boolean} bool
27499     *        - true signals that this is an audio player
27500     *        - false signals that this is not an audio player
27501     *
27502     * @return {boolean}
27503     *         The current value of isAudio when getting
27504     */
27505    ;
27506
27507    _proto.isAudio = function isAudio(bool) {
27508      if (bool !== undefined) {
27509        this.isAudio_ = !!bool;
27510        return;
27511      }
27512
27513      return !!this.isAudio_;
27514    }
27515    /**
27516     * A helper method for adding a {@link TextTrack} to our
27517     * {@link TextTrackList}.
27518     *
27519     * In addition to the W3C settings we allow adding additional info through options.
27520     *
27521     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
27522     *
27523     * @param {string} [kind]
27524     *        the kind of TextTrack you are adding
27525     *
27526     * @param {string} [label]
27527     *        the label to give the TextTrack label
27528     *
27529     * @param {string} [language]
27530     *        the language to set on the TextTrack
27531     *
27532     * @return {TextTrack|undefined}
27533     *         the TextTrack that was added or undefined
27534     *         if there is no tech
27535     */
27536    ;
27537
27538    _proto.addTextTrack = function addTextTrack(kind, label, language) {
27539      if (this.tech_) {
27540        return this.tech_.addTextTrack(kind, label, language);
27541      }
27542    }
27543    /**
27544     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
27545     * When manualCleanup is set to false, the track will be automatically removed
27546     * on source changes.
27547     *
27548     * @param {Object} options
27549     *        Options to pass to {@link HTMLTrackElement} during creation. See
27550     *        {@link HTMLTrackElement} for object properties that you should use.
27551     *
27552     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
27553     *                                       removed on a source change
27554     *
27555     * @return {HtmlTrackElement}
27556     *         the HTMLTrackElement that was created and added
27557     *         to the HtmlTrackElementList and the remote
27558     *         TextTrackList
27559     *
27560     * @deprecated The default value of the "manualCleanup" parameter will default
27561     *             to "false" in upcoming versions of Video.js
27562     */
27563    ;
27564
27565    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
27566      if (this.tech_) {
27567        return this.tech_.addRemoteTextTrack(options, manualCleanup);
27568      }
27569    }
27570    /**
27571     * Remove a remote {@link TextTrack} from the respective
27572     * {@link TextTrackList} and {@link HtmlTrackElementList}.
27573     *
27574     * @param {Object} track
27575     *        Remote {@link TextTrack} to remove
27576     *
27577     * @return {undefined}
27578     *         does not return anything
27579     */
27580    ;
27581
27582    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
27583      if (obj === void 0) {
27584        obj = {};
27585      }
27586
27587      var _obj = obj,
27588          track = _obj.track;
27589
27590      if (!track) {
27591        track = obj;
27592      } // destructure the input into an object with a track argument, defaulting to arguments[0]
27593      // default the whole argument to an empty object if nothing was passed in
27594
27595
27596      if (this.tech_) {
27597        return this.tech_.removeRemoteTextTrack(track);
27598      }
27599    }
27600    /**
27601     * Gets available media playback quality metrics as specified by the W3C's Media
27602     * Playback Quality API.
27603     *
27604     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
27605     *
27606     * @return {Object|undefined}
27607     *         An object with supported media playback quality metrics or undefined if there
27608     *         is no tech or the tech does not support it.
27609     */
27610    ;
27611
27612    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
27613      return this.techGet_('getVideoPlaybackQuality');
27614    }
27615    /**
27616     * Get video width
27617     *
27618     * @return {number}
27619     *         current video width
27620     */
27621    ;
27622
27623    _proto.videoWidth = function videoWidth() {
27624      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
27625    }
27626    /**
27627     * Get video height
27628     *
27629     * @return {number}
27630     *         current video height
27631     */
27632    ;
27633
27634    _proto.videoHeight = function videoHeight() {
27635      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
27636    }
27637    /**
27638     * The player's language code
27639     * NOTE: The language should be set in the player options if you want the
27640     * the controls to be built with a specific language. Changing the language
27641     * later will not update controls text.
27642     *
27643     * @param {string} [code]
27644     *        the language code to set the player to
27645     *
27646     * @return {string}
27647     *         The current language code when getting
27648     */
27649    ;
27650
27651    _proto.language = function language(code) {
27652      if (code === undefined) {
27653        return this.language_;
27654      }
27655
27656      this.language_ = String(code).toLowerCase();
27657    }
27658    /**
27659     * Get the player's language dictionary
27660     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
27661     * Languages specified directly in the player options have precedence
27662     *
27663     * @return {Array}
27664     *         An array of of supported languages
27665     */
27666    ;
27667
27668    _proto.languages = function languages() {
27669      return mergeOptions(Player.prototype.options_.languages, this.languages_);
27670    }
27671    /**
27672     * returns a JavaScript object reperesenting the current track
27673     * information. **DOES not return it as JSON**
27674     *
27675     * @return {Object}
27676     *         Object representing the current of track info
27677     */
27678    ;
27679
27680    _proto.toJSON = function toJSON() {
27681      var options = mergeOptions(this.options_);
27682      var tracks = options.tracks;
27683      options.tracks = [];
27684
27685      for (var i = 0; i < tracks.length; i++) {
27686        var track = tracks[i]; // deep merge tracks and null out player so no circular references
27687
27688        track = mergeOptions(track);
27689        track.player = undefined;
27690        options.tracks[i] = track;
27691      }
27692
27693      return options;
27694    }
27695    /**
27696     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
27697     * component) that immediately overlays the player with arbitrary
27698     * content and removes itself when closed.
27699     *
27700     * @param {string|Function|Element|Array|null} content
27701     *        Same as {@link ModalDialog#content}'s param of the same name.
27702     *        The most straight-forward usage is to provide a string or DOM
27703     *        element.
27704     *
27705     * @param {Object} [options]
27706     *        Extra options which will be passed on to the {@link ModalDialog}.
27707     *
27708     * @return {ModalDialog}
27709     *         the {@link ModalDialog} that was created
27710     */
27711    ;
27712
27713    _proto.createModal = function createModal(content, options) {
27714      var _this16 = this;
27715
27716      options = options || {};
27717      options.content = content || '';
27718      var modal = new ModalDialog(this, options);
27719      this.addChild(modal);
27720      modal.on('dispose', function () {
27721        _this16.removeChild(modal);
27722      });
27723      modal.open();
27724      return modal;
27725    }
27726    /**
27727     * Change breakpoint classes when the player resizes.
27728     *
27729     * @private
27730     */
27731    ;
27732
27733    _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
27734      if (!this.responsive()) {
27735        return;
27736      }
27737
27738      var currentBreakpoint = this.currentBreakpoint();
27739      var currentWidth = this.currentWidth();
27740
27741      for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
27742        var candidateBreakpoint = BREAKPOINT_ORDER[i];
27743        var maxWidth = this.breakpoints_[candidateBreakpoint];
27744
27745        if (currentWidth <= maxWidth) {
27746          // The current breakpoint did not change, nothing to do.
27747          if (currentBreakpoint === candidateBreakpoint) {
27748            return;
27749          } // Only remove a class if there is a current breakpoint.
27750
27751
27752          if (currentBreakpoint) {
27753            this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
27754          }
27755
27756          this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
27757          this.breakpoint_ = candidateBreakpoint;
27758          break;
27759        }
27760      }
27761    }
27762    /**
27763     * Removes the current breakpoint.
27764     *
27765     * @private
27766     */
27767    ;
27768
27769    _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
27770      var className = this.currentBreakpointClass();
27771      this.breakpoint_ = '';
27772
27773      if (className) {
27774        this.removeClass(className);
27775      }
27776    }
27777    /**
27778     * Get or set breakpoints on the player.
27779     *
27780     * Calling this method with an object or `true` will remove any previous
27781     * custom breakpoints and start from the defaults again.
27782     *
27783     * @param  {Object|boolean} [breakpoints]
27784     *         If an object is given, it can be used to provide custom
27785     *         breakpoints. If `true` is given, will set default breakpoints.
27786     *         If this argument is not given, will simply return the current
27787     *         breakpoints.
27788     *
27789     * @param  {number} [breakpoints.tiny]
27790     *         The maximum width for the "vjs-layout-tiny" class.
27791     *
27792     * @param  {number} [breakpoints.xsmall]
27793     *         The maximum width for the "vjs-layout-x-small" class.
27794     *
27795     * @param  {number} [breakpoints.small]
27796     *         The maximum width for the "vjs-layout-small" class.
27797     *
27798     * @param  {number} [breakpoints.medium]
27799     *         The maximum width for the "vjs-layout-medium" class.
27800     *
27801     * @param  {number} [breakpoints.large]
27802     *         The maximum width for the "vjs-layout-large" class.
27803     *
27804     * @param  {number} [breakpoints.xlarge]
27805     *         The maximum width for the "vjs-layout-x-large" class.
27806     *
27807     * @param  {number} [breakpoints.huge]
27808     *         The maximum width for the "vjs-layout-huge" class.
27809     *
27810     * @return {Object}
27811     *         An object mapping breakpoint names to maximum width values.
27812     */
27813    ;
27814
27815    _proto.breakpoints = function breakpoints(_breakpoints) {
27816      // Used as a getter.
27817      if (_breakpoints === undefined) {
27818        return assign(this.breakpoints_);
27819      }
27820
27821      this.breakpoint_ = '';
27822      this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
27823      // selected breakpoint.
27824
27825      this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
27826
27827      return assign(this.breakpoints_);
27828    }
27829    /**
27830     * Get or set a flag indicating whether or not this player should adjust
27831     * its UI based on its dimensions.
27832     *
27833     * @param  {boolean} value
27834     *         Should be `true` if the player should adjust its UI based on its
27835     *         dimensions; otherwise, should be `false`.
27836     *
27837     * @return {boolean}
27838     *         Will be `true` if this player should adjust its UI based on its
27839     *         dimensions; otherwise, will be `false`.
27840     */
27841    ;
27842
27843    _proto.responsive = function responsive(value) {
27844      // Used as a getter.
27845      if (value === undefined) {
27846        return this.responsive_;
27847      }
27848
27849      value = Boolean(value);
27850      var current = this.responsive_; // Nothing changed.
27851
27852      if (value === current) {
27853        return;
27854      } // The value actually changed, set it.
27855
27856
27857      this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
27858      // player is now responsive.
27859
27860      if (value) {
27861        this.on('playerresize', this.updateCurrentBreakpoint_);
27862        this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
27863      } else {
27864        this.off('playerresize', this.updateCurrentBreakpoint_);
27865        this.removeCurrentBreakpoint_();
27866      }
27867
27868      return value;
27869    }
27870    /**
27871     * Get current breakpoint name, if any.
27872     *
27873     * @return {string}
27874     *         If there is currently a breakpoint set, returns a the key from the
27875     *         breakpoints object matching it. Otherwise, returns an empty string.
27876     */
27877    ;
27878
27879    _proto.currentBreakpoint = function currentBreakpoint() {
27880      return this.breakpoint_;
27881    }
27882    /**
27883     * Get the current breakpoint class name.
27884     *
27885     * @return {string}
27886     *         The matching class name (e.g. `"vjs-layout-tiny"` or
27887     *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
27888     *         there is no current breakpoint.
27889     */
27890    ;
27891
27892    _proto.currentBreakpointClass = function currentBreakpointClass() {
27893      return BREAKPOINT_CLASSES[this.breakpoint_] || '';
27894    }
27895    /**
27896     * An object that describes a single piece of media.
27897     *
27898     * Properties that are not part of this type description will be retained; so,
27899     * this can be viewed as a generic metadata storage mechanism as well.
27900     *
27901     * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
27902     * @typedef  {Object} Player~MediaObject
27903     *
27904     * @property {string} [album]
27905     *           Unused, except if this object is passed to the `MediaSession`
27906     *           API.
27907     *
27908     * @property {string} [artist]
27909     *           Unused, except if this object is passed to the `MediaSession`
27910     *           API.
27911     *
27912     * @property {Object[]} [artwork]
27913     *           Unused, except if this object is passed to the `MediaSession`
27914     *           API. If not specified, will be populated via the `poster`, if
27915     *           available.
27916     *
27917     * @property {string} [poster]
27918     *           URL to an image that will display before playback.
27919     *
27920     * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
27921     *           A single source object, an array of source objects, or a string
27922     *           referencing a URL to a media source. It is _highly recommended_
27923     *           that an object or array of objects is used here, so that source
27924     *           selection algorithms can take the `type` into account.
27925     *
27926     * @property {string} [title]
27927     *           Unused, except if this object is passed to the `MediaSession`
27928     *           API.
27929     *
27930     * @property {Object[]} [textTracks]
27931     *           An array of objects to be used to create text tracks, following
27932     *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
27933     *           For ease of removal, these will be created as "remote" text
27934     *           tracks and set to automatically clean up on source changes.
27935     *
27936     *           These objects may have properties like `src`, `kind`, `label`,
27937     *           and `language`, see {@link Tech#createRemoteTextTrack}.
27938     */
27939
27940    /**
27941     * Populate the player using a {@link Player~MediaObject|MediaObject}.
27942     *
27943     * @param  {Player~MediaObject} media
27944     *         A media object.
27945     *
27946     * @param  {Function} ready
27947     *         A callback to be called when the player is ready.
27948     */
27949    ;
27950
27951    _proto.loadMedia = function loadMedia(media, ready) {
27952      var _this17 = this;
27953
27954      if (!media || typeof media !== 'object') {
27955        return;
27956      }
27957
27958      this.reset(); // Clone the media object so it cannot be mutated from outside.
27959
27960      this.cache_.media = mergeOptions(media);
27961      var _this$cache_$media = this.cache_.media,
27962          artwork = _this$cache_$media.artwork,
27963          poster = _this$cache_$media.poster,
27964          src = _this$cache_$media.src,
27965          textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
27966
27967      if (!artwork && poster) {
27968        this.cache_.media.artwork = [{
27969          src: poster,
27970          type: getMimetype(poster)
27971        }];
27972      }
27973
27974      if (src) {
27975        this.src(src);
27976      }
27977
27978      if (poster) {
27979        this.poster(poster);
27980      }
27981
27982      if (Array.isArray(textTracks)) {
27983        textTracks.forEach(function (tt) {
27984          return _this17.addRemoteTextTrack(tt, false);
27985        });
27986      }
27987
27988      this.ready(ready);
27989    }
27990    /**
27991     * Get a clone of the current {@link Player~MediaObject} for this player.
27992     *
27993     * If the `loadMedia` method has not been used, will attempt to return a
27994     * {@link Player~MediaObject} based on the current state of the player.
27995     *
27996     * @return {Player~MediaObject}
27997     */
27998    ;
27999
28000    _proto.getMedia = function getMedia() {
28001      if (!this.cache_.media) {
28002        var poster = this.poster();
28003        var src = this.currentSources();
28004        var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
28005          return {
28006            kind: tt.kind,
28007            label: tt.label,
28008            language: tt.language,
28009            src: tt.src
28010          };
28011        });
28012        var media = {
28013          src: src,
28014          textTracks: textTracks
28015        };
28016
28017        if (poster) {
28018          media.poster = poster;
28019          media.artwork = [{
28020            src: media.poster,
28021            type: getMimetype(media.poster)
28022          }];
28023        }
28024
28025        return media;
28026      }
28027
28028      return mergeOptions(this.cache_.media);
28029    }
28030    /**
28031     * Gets tag settings
28032     *
28033     * @param {Element} tag
28034     *        The player tag
28035     *
28036     * @return {Object}
28037     *         An object containing all of the settings
28038     *         for a player tag
28039     */
28040    ;
28041
28042    Player.getTagSettings = function getTagSettings(tag) {
28043      var baseOptions = {
28044        sources: [],
28045        tracks: []
28046      };
28047      var tagOptions = getAttributes(tag);
28048      var dataSetup = tagOptions['data-setup'];
28049
28050      if (hasClass(tag, 'vjs-fill')) {
28051        tagOptions.fill = true;
28052      }
28053
28054      if (hasClass(tag, 'vjs-fluid')) {
28055        tagOptions.fluid = true;
28056      } // Check if data-setup attr exists.
28057
28058
28059      if (dataSetup !== null) {
28060        // Parse options JSON
28061        // If empty string, make it a parsable json object.
28062        var _safeParseTuple = tuple(dataSetup || '{}'),
28063            err = _safeParseTuple[0],
28064            data = _safeParseTuple[1];
28065
28066        if (err) {
28067          log.error(err);
28068        }
28069
28070        assign(tagOptions, data);
28071      }
28072
28073      assign(baseOptions, tagOptions); // Get tag children settings
28074
28075      if (tag.hasChildNodes()) {
28076        var children = tag.childNodes;
28077
28078        for (var i = 0, j = children.length; i < j; i++) {
28079          var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
28080
28081          var childName = child.nodeName.toLowerCase();
28082
28083          if (childName === 'source') {
28084            baseOptions.sources.push(getAttributes(child));
28085          } else if (childName === 'track') {
28086            baseOptions.tracks.push(getAttributes(child));
28087          }
28088        }
28089      }
28090
28091      return baseOptions;
28092    }
28093    /**
28094     * Determine whether or not flexbox is supported
28095     *
28096     * @return {boolean}
28097     *         - true if flexbox is supported
28098     *         - false if flexbox is not supported
28099     */
28100    ;
28101
28102    _proto.flexNotSupported_ = function flexNotSupported_() {
28103      var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
28104      // common flex features that we can rely on when checking for flex support.
28105
28106      return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || // IE10-specific (2012 flex spec), available for completeness
28107      'msFlexOrder' in elem.style);
28108    }
28109    /**
28110     * Set debug mode to enable/disable logs at info level.
28111     *
28112     * @param {boolean} enabled
28113     * @fires Player#debugon
28114     * @fires Player#debugoff
28115     */
28116    ;
28117
28118    _proto.debug = function debug(enabled) {
28119      if (enabled === undefined) {
28120        return this.debugEnabled_;
28121      }
28122
28123      if (enabled) {
28124        this.trigger('debugon');
28125        this.previousLogLevel_ = this.log.level;
28126        this.log.level('debug');
28127        this.debugEnabled_ = true;
28128      } else {
28129        this.trigger('debugoff');
28130        this.log.level(this.previousLogLevel_);
28131        this.previousLogLevel_ = undefined;
28132        this.debugEnabled_ = false;
28133      }
28134    };
28135
28136    return Player;
28137  }(Component);
28138  /**
28139   * Get the {@link VideoTrackList}
28140   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
28141   *
28142   * @return {VideoTrackList}
28143   *         the current video track list
28144   *
28145   * @method Player.prototype.videoTracks
28146   */
28147
28148  /**
28149   * Get the {@link AudioTrackList}
28150   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
28151   *
28152   * @return {AudioTrackList}
28153   *         the current audio track list
28154   *
28155   * @method Player.prototype.audioTracks
28156   */
28157
28158  /**
28159   * Get the {@link TextTrackList}
28160   *
28161   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
28162   *
28163   * @return {TextTrackList}
28164   *         the current text track list
28165   *
28166   * @method Player.prototype.textTracks
28167   */
28168
28169  /**
28170   * Get the remote {@link TextTrackList}
28171   *
28172   * @return {TextTrackList}
28173   *         The current remote text track list
28174   *
28175   * @method Player.prototype.remoteTextTracks
28176   */
28177
28178  /**
28179   * Get the remote {@link HtmlTrackElementList} tracks.
28180   *
28181   * @return {HtmlTrackElementList}
28182   *         The current remote text track element list
28183   *
28184   * @method Player.prototype.remoteTextTrackEls
28185   */
28186
28187
28188  ALL.names.forEach(function (name) {
28189    var props = ALL[name];
28190
28191    Player.prototype[props.getterName] = function () {
28192      if (this.tech_) {
28193        return this.tech_[props.getterName]();
28194      } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
28195      // these will be passed to the tech during loading
28196
28197
28198      this[props.privateName] = this[props.privateName] || new props.ListClass();
28199      return this[props.privateName];
28200    };
28201  });
28202  /**
28203   * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
28204   * sets the `crossOrigin` property on the `<video>` tag to control the CORS
28205   * behavior.
28206   *
28207   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
28208   *
28209   * @param {string} [value]
28210   *        The value to set the `Player`'s crossorigin to. If an argument is
28211   *        given, must be one of `anonymous` or `use-credentials`.
28212   *
28213   * @return {string|undefined}
28214   *         - The current crossorigin value of the `Player` when getting.
28215   *         - undefined when setting
28216   */
28217
28218  Player.prototype.crossorigin = Player.prototype.crossOrigin;
28219  /**
28220   * Global enumeration of players.
28221   *
28222   * The keys are the player IDs and the values are either the {@link Player}
28223   * instance or `null` for disposed players.
28224   *
28225   * @type {Object}
28226   */
28227
28228  Player.players = {};
28229  var navigator = window$1.navigator;
28230  /*
28231   * Player instance options, surfaced using options
28232   * options = Player.prototype.options_
28233   * Make changes in options, not here.
28234   *
28235   * @type {Object}
28236   * @private
28237   */
28238
28239  Player.prototype.options_ = {
28240    // Default order of fallback technology
28241    techOrder: Tech.defaultTechOrder_,
28242    html5: {},
28243    flash: {},
28244    // default inactivity timeout
28245    inactivityTimeout: 2000,
28246    // default playback rates
28247    playbackRates: [],
28248    // Add playback rate selection by adding rates
28249    // 'playbackRates': [0.5, 1, 1.5, 2],
28250    liveui: false,
28251    // Included control sets
28252    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
28253    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
28254    // locales and their language translations
28255    languages: {},
28256    // Default message to show when a video cannot be played.
28257    notSupportedMessage: 'No compatible source was found for this media.',
28258    fullscreen: {
28259      options: {
28260        navigationUI: 'hide'
28261      }
28262    },
28263    breakpoints: {},
28264    responsive: false
28265  };
28266  [
28267  /**
28268   * Returns whether or not the player is in the "ended" state.
28269   *
28270   * @return {Boolean} True if the player is in the ended state, false if not.
28271   * @method Player#ended
28272   */
28273  'ended',
28274  /**
28275   * Returns whether or not the player is in the "seeking" state.
28276   *
28277   * @return {Boolean} True if the player is in the seeking state, false if not.
28278   * @method Player#seeking
28279   */
28280  'seeking',
28281  /**
28282   * Returns the TimeRanges of the media that are currently available
28283   * for seeking to.
28284   *
28285   * @return {TimeRanges} the seekable intervals of the media timeline
28286   * @method Player#seekable
28287   */
28288  'seekable',
28289  /**
28290   * Returns the current state of network activity for the element, from
28291   * the codes in the list below.
28292   * - NETWORK_EMPTY (numeric value 0)
28293   *   The element has not yet been initialised. All attributes are in
28294   *   their initial states.
28295   * - NETWORK_IDLE (numeric value 1)
28296   *   The element's resource selection algorithm is active and has
28297   *   selected a resource, but it is not actually using the network at
28298   *   this time.
28299   * - NETWORK_LOADING (numeric value 2)
28300   *   The user agent is actively trying to download data.
28301   * - NETWORK_NO_SOURCE (numeric value 3)
28302   *   The element's resource selection algorithm is active, but it has
28303   *   not yet found a resource to use.
28304   *
28305   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
28306   * @return {number} the current network activity state
28307   * @method Player#networkState
28308   */
28309  'networkState',
28310  /**
28311   * Returns a value that expresses the current state of the element
28312   * with respect to rendering the current playback position, from the
28313   * codes in the list below.
28314   * - HAVE_NOTHING (numeric value 0)
28315   *   No information regarding the media resource is available.
28316   * - HAVE_METADATA (numeric value 1)
28317   *   Enough of the resource has been obtained that the duration of the
28318   *   resource is available.
28319   * - HAVE_CURRENT_DATA (numeric value 2)
28320   *   Data for the immediate current playback position is available.
28321   * - HAVE_FUTURE_DATA (numeric value 3)
28322   *   Data for the immediate current playback position is available, as
28323   *   well as enough data for the user agent to advance the current
28324   *   playback position in the direction of playback.
28325   * - HAVE_ENOUGH_DATA (numeric value 4)
28326   *   The user agent estimates that enough data is available for
28327   *   playback to proceed uninterrupted.
28328   *
28329   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
28330   * @return {number} the current playback rendering state
28331   * @method Player#readyState
28332   */
28333  'readyState'].forEach(function (fn) {
28334    Player.prototype[fn] = function () {
28335      return this.techGet_(fn);
28336    };
28337  });
28338  TECH_EVENTS_RETRIGGER.forEach(function (event) {
28339    Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
28340      return this.trigger(event);
28341    };
28342  });
28343  /**
28344   * Fired when the player has initial duration and dimension information
28345   *
28346   * @event Player#loadedmetadata
28347   * @type {EventTarget~Event}
28348   */
28349
28350  /**
28351   * Fired when the player has downloaded data at the current playback position
28352   *
28353   * @event Player#loadeddata
28354   * @type {EventTarget~Event}
28355   */
28356
28357  /**
28358   * Fired when the current playback position has changed *
28359   * During playback this is fired every 15-250 milliseconds, depending on the
28360   * playback technology in use.
28361   *
28362   * @event Player#timeupdate
28363   * @type {EventTarget~Event}
28364   */
28365
28366  /**
28367   * Fired when the volume changes
28368   *
28369   * @event Player#volumechange
28370   * @type {EventTarget~Event}
28371   */
28372
28373  /**
28374   * Reports whether or not a player has a plugin available.
28375   *
28376   * This does not report whether or not the plugin has ever been initialized
28377   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
28378   *
28379   * @method Player#hasPlugin
28380   * @param  {string}  name
28381   *         The name of a plugin.
28382   *
28383   * @return {boolean}
28384   *         Whether or not this player has the requested plugin available.
28385   */
28386
28387  /**
28388   * Reports whether or not a player is using a plugin by name.
28389   *
28390   * For basic plugins, this only reports whether the plugin has _ever_ been
28391   * initialized on this player.
28392   *
28393   * @method Player#usingPlugin
28394   * @param  {string} name
28395   *         The name of a plugin.
28396   *
28397   * @return {boolean}
28398   *         Whether or not this player is using the requested plugin.
28399   */
28400
28401  Component.registerComponent('Player', Player);
28402
28403  var setPrototypeOf = createCommonjsModule(function (module) {
28404    function _setPrototypeOf(o, p) {
28405      module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
28406        o.__proto__ = p;
28407        return o;
28408      };
28409
28410      return _setPrototypeOf(o, p);
28411    }
28412
28413    module.exports = _setPrototypeOf;
28414  });
28415
28416  function _isNativeReflectConstruct() {
28417    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
28418    if (Reflect.construct.sham) return false;
28419    if (typeof Proxy === "function") return true;
28420
28421    try {
28422      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
28423      return true;
28424    } catch (e) {
28425      return false;
28426    }
28427  }
28428
28429  var isNativeReflectConstruct = _isNativeReflectConstruct;
28430
28431  var construct = createCommonjsModule(function (module) {
28432    function _construct(Parent, args, Class) {
28433      if (isNativeReflectConstruct()) {
28434        module.exports = _construct = Reflect.construct;
28435      } else {
28436        module.exports = _construct = function _construct(Parent, args, Class) {
28437          var a = [null];
28438          a.push.apply(a, args);
28439          var Constructor = Function.bind.apply(Parent, a);
28440          var instance = new Constructor();
28441          if (Class) setPrototypeOf(instance, Class.prototype);
28442          return instance;
28443        };
28444      }
28445
28446      return _construct.apply(null, arguments);
28447    }
28448
28449    module.exports = _construct;
28450  });
28451
28452  /**
28453   * The base plugin name.
28454   *
28455   * @private
28456   * @constant
28457   * @type {string}
28458   */
28459
28460  var BASE_PLUGIN_NAME = 'plugin';
28461  /**
28462   * The key on which a player's active plugins cache is stored.
28463   *
28464   * @private
28465   * @constant
28466   * @type     {string}
28467   */
28468
28469  var PLUGIN_CACHE_KEY = 'activePlugins_';
28470  /**
28471   * Stores registered plugins in a private space.
28472   *
28473   * @private
28474   * @type    {Object}
28475   */
28476
28477  var pluginStorage = {};
28478  /**
28479   * Reports whether or not a plugin has been registered.
28480   *
28481   * @private
28482   * @param   {string} name
28483   *          The name of a plugin.
28484   *
28485   * @return {boolean}
28486   *          Whether or not the plugin has been registered.
28487   */
28488
28489  var pluginExists = function pluginExists(name) {
28490    return pluginStorage.hasOwnProperty(name);
28491  };
28492  /**
28493   * Get a single registered plugin by name.
28494   *
28495   * @private
28496   * @param   {string} name
28497   *          The name of a plugin.
28498   *
28499   * @return {Function|undefined}
28500   *          The plugin (or undefined).
28501   */
28502
28503
28504  var getPlugin = function getPlugin(name) {
28505    return pluginExists(name) ? pluginStorage[name] : undefined;
28506  };
28507  /**
28508   * Marks a plugin as "active" on a player.
28509   *
28510   * Also, ensures that the player has an object for tracking active plugins.
28511   *
28512   * @private
28513   * @param   {Player} player
28514   *          A Video.js player instance.
28515   *
28516   * @param   {string} name
28517   *          The name of a plugin.
28518   */
28519
28520
28521  var markPluginAsActive = function markPluginAsActive(player, name) {
28522    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
28523    player[PLUGIN_CACHE_KEY][name] = true;
28524  };
28525  /**
28526   * Triggers a pair of plugin setup events.
28527   *
28528   * @private
28529   * @param  {Player} player
28530   *         A Video.js player instance.
28531   *
28532   * @param  {Plugin~PluginEventHash} hash
28533   *         A plugin event hash.
28534   *
28535   * @param  {boolean} [before]
28536   *         If true, prefixes the event name with "before". In other words,
28537   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
28538   */
28539
28540
28541  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
28542    var eventName = (before ? 'before' : '') + 'pluginsetup';
28543    player.trigger(eventName, hash);
28544    player.trigger(eventName + ':' + hash.name, hash);
28545  };
28546  /**
28547   * Takes a basic plugin function and returns a wrapper function which marks
28548   * on the player that the plugin has been activated.
28549   *
28550   * @private
28551   * @param   {string} name
28552   *          The name of the plugin.
28553   *
28554   * @param   {Function} plugin
28555   *          The basic plugin.
28556   *
28557   * @return {Function}
28558   *          A wrapper function for the given plugin.
28559   */
28560
28561
28562  var createBasicPlugin = function createBasicPlugin(name, plugin) {
28563    var basicPluginWrapper = function basicPluginWrapper() {
28564      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
28565      // regardless, but we want the hash to be consistent with the hash provided
28566      // for advanced plugins.
28567      //
28568      // The only potentially counter-intuitive thing here is the `instance` in
28569      // the "pluginsetup" event is the value returned by the `plugin` function.
28570      triggerSetupEvent(this, {
28571        name: name,
28572        plugin: plugin,
28573        instance: null
28574      }, true);
28575      var instance = plugin.apply(this, arguments);
28576      markPluginAsActive(this, name);
28577      triggerSetupEvent(this, {
28578        name: name,
28579        plugin: plugin,
28580        instance: instance
28581      });
28582      return instance;
28583    };
28584
vendor: 8,993 bytes, lines 28585-28869
28585    Object.keys(plugin).forEach(function (prop) {
28586      basicPluginWrapper[prop] = plugin[prop];
28587    });
28588    return basicPluginWrapper;
28589  };
28590  /**
28591   * Takes a plugin sub-class and returns a factory function for generating
28592   * instances of it.
28593   *
28594   * This factory function will replace itself with an instance of the requested
28595   * sub-class of Plugin.
28596   *
28597   * @private
28598   * @param   {string} name
28599   *          The name of the plugin.
28600   *
28601   * @param   {Plugin} PluginSubClass
28602   *          The advanced plugin.
28603   *
28604   * @return {Function}
28605   */
28606
28607
28608  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
28609    // Add a `name` property to the plugin prototype so that each plugin can
28610    // refer to itself by name.
28611    PluginSubClass.prototype.name = name;
28612    return function () {
28613      triggerSetupEvent(this, {
28614        name: name,
28615        plugin: PluginSubClass,
28616        instance: null
28617      }, true);
28618
28619      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
28620        args[_key] = arguments[_key];
28621      }
28622
28623      var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
28624
28625
28626      this[name] = function () {
28627        return instance;
28628      };
28629
28630      triggerSetupEvent(this, instance.getEventHash());
28631      return instance;
28632    };
28633  };
28634  /**
28635   * Parent class for all advanced plugins.
28636   *
28637   * @mixes   module:evented~EventedMixin
28638   * @mixes   module:stateful~StatefulMixin
28639   * @fires   Player#beforepluginsetup
28640   * @fires   Player#beforepluginsetup:$name
28641   * @fires   Player#pluginsetup
28642   * @fires   Player#pluginsetup:$name
28643   * @listens Player#dispose
28644   * @throws  {Error}
28645   *          If attempting to instantiate the base {@link Plugin} class
28646   *          directly instead of via a sub-class.
28647   */
28648
28649
28650  var Plugin = /*#__PURE__*/function () {
28651    /**
28652     * Creates an instance of this class.
28653     *
28654     * Sub-classes should call `super` to ensure plugins are properly initialized.
28655     *
28656     * @param {Player} player
28657     *        A Video.js player instance.
28658     */
28659    function Plugin(player) {
28660      if (this.constructor === Plugin) {
28661        throw new Error('Plugin must be sub-classed; not directly instantiated.');
28662      }
28663
28664      this.player = player;
28665
28666      if (!this.log) {
28667        this.log = this.player.log.createLogger(this.name);
28668      } // Make this object evented, but remove the added `trigger` method so we
28669      // use the prototype version instead.
28670
28671
28672      evented(this);
28673      delete this.trigger;
28674      stateful(this, this.constructor.defaultState);
28675      markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
28676      // it later easily.
28677
28678      this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
28679
28680      player.on('dispose', this.dispose);
28681    }
28682    /**
28683     * Get the version of the plugin that was set on <pluginName>.VERSION
28684     */
28685
28686
28687    var _proto = Plugin.prototype;
28688
28689    _proto.version = function version() {
28690      return this.constructor.VERSION;
28691    }
28692    /**
28693     * Each event triggered by plugins includes a hash of additional data with
28694     * conventional properties.
28695     *
28696     * This returns that object or mutates an existing hash.
28697     *
28698     * @param   {Object} [hash={}]
28699     *          An object to be used as event an event hash.
28700     *
28701     * @return {Plugin~PluginEventHash}
28702     *          An event hash object with provided properties mixed-in.
28703     */
28704    ;
28705
28706    _proto.getEventHash = function getEventHash(hash) {
28707      if (hash === void 0) {
28708        hash = {};
28709      }
28710
28711      hash.name = this.name;
28712      hash.plugin = this.constructor;
28713      hash.instance = this;
28714      return hash;
28715    }
28716    /**
28717     * Triggers an event on the plugin object and overrides
28718     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
28719     *
28720     * @param   {string|Object} event
28721     *          An event type or an object with a type property.
28722     *
28723     * @param   {Object} [hash={}]
28724     *          Additional data hash to merge with a
28725     *          {@link Plugin~PluginEventHash|PluginEventHash}.
28726     *
28727     * @return {boolean}
28728     *          Whether or not default was prevented.
28729     */
28730    ;
28731
28732    _proto.trigger = function trigger$1(event, hash) {
28733      if (hash === void 0) {
28734        hash = {};
28735      }
28736
28737      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
28738    }
28739    /**
28740     * Handles "statechanged" events on the plugin. No-op by default, override by
28741     * subclassing.
28742     *
28743     * @abstract
28744     * @param    {Event} e
28745     *           An event object provided by a "statechanged" event.
28746     *
28747     * @param    {Object} e.changes
28748     *           An object describing changes that occurred with the "statechanged"
28749     *           event.
28750     */
28751    ;
28752
28753    _proto.handleStateChanged = function handleStateChanged(e) {}
28754    /**
28755     * Disposes a plugin.
28756     *
28757     * Subclasses can override this if they want, but for the sake of safety,
28758     * it's probably best to subscribe the "dispose" event.
28759     *
28760     * @fires Plugin#dispose
28761     */
28762    ;
28763
28764    _proto.dispose = function dispose() {
28765      var name = this.name,
28766          player = this.player;
28767      /**
28768       * Signals that a advanced plugin is about to be disposed.
28769       *
28770       * @event Plugin#dispose
28771       * @type  {EventTarget~Event}
28772       */
28773
28774      this.trigger('dispose');
28775      this.off();
28776      player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
28777      // references between the player and the plugin instance and nulling out
28778      // the plugin's state and replacing methods with a function that throws.
28779
28780      player[PLUGIN_CACHE_KEY][name] = false;
28781      this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
28782      // function, so that the plugin is ready to be set up again.
28783
28784      player[name] = createPluginFactory(name, pluginStorage[name]);
28785    }
28786    /**
28787     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
28788     *
28789     * @param   {string|Function} plugin
28790     *          If a string, matches the name of a plugin. If a function, will be
28791     *          tested directly.
28792     *
28793     * @return {boolean}
28794     *          Whether or not a plugin is a basic plugin.
28795     */
28796    ;
28797
28798    Plugin.isBasic = function isBasic(plugin) {
28799      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
28800      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
28801    }
28802    /**
28803     * Register a Video.js plugin.
28804     *
28805     * @param   {string} name
28806     *          The name of the plugin to be registered. Must be a string and
28807     *          must not match an existing plugin or a method on the `Player`
28808     *          prototype.
28809     *
28810     * @param   {Function} plugin
28811     *          A sub-class of `Plugin` or a function for basic plugins.
28812     *
28813     * @return {Function}
28814     *          For advanced plugins, a factory function for that plugin. For
28815     *          basic plugins, a wrapper function that initializes the plugin.
28816     */
28817    ;
28818
28819    Plugin.registerPlugin = function registerPlugin(name, plugin) {
28820      if (typeof name !== 'string') {
28821        throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
28822      }
28823
28824      if (pluginExists(name)) {
28825        log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
28826      } else if (Player.prototype.hasOwnProperty(name)) {
28827        throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
28828      }
28829
28830      if (typeof plugin !== 'function') {
28831        throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
28832      }
28833
28834      pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
28835      // the base Plugin class).
28836
28837      if (name !== BASE_PLUGIN_NAME) {
28838        if (Plugin.isBasic(plugin)) {
28839          Player.prototype[name] = createBasicPlugin(name, plugin);
28840        } else {
28841          Player.prototype[name] = createPluginFactory(name, plugin);
28842        }
28843      }
28844
28845      return plugin;
28846    }
28847    /**
28848     * De-register a Video.js plugin.
28849     *
28850     * @param  {string} name
28851     *         The name of the plugin to be de-registered. Must be a string that
28852     *         matches an existing plugin.
28853     *
28854     * @throws {Error}
28855     *         If an attempt is made to de-register the base plugin.
28856     */
28857    ;
28858
28859    Plugin.deregisterPlugin = function deregisterPlugin(name) {
28860      if (name === BASE_PLUGIN_NAME) {
28861        throw new Error('Cannot de-register base plugin.');
28862      }
28863
28864      if (pluginExists(name)) {
28865        delete pluginStorage[name];
28866        delete Player.prototype[name];
28867      }
28868    }
28869    /**
28870     * Gets an object containing multiple Video.js plugins.
28871     *
28872     * @param   {Array} [names]
28873     *          If provided, should be an array of plugin names. Defaults to _all_
28874     *          plugin names.
28875     *
28876     * @return {Object|undefined}
28877     *          An object containing plugin(s) associated with their name(s) or
28878     *          `undefined` if no matching plugins exist).
28879     */
28880    ;
28881
28882    Plugin.getPlugins = function getPlugins(names) {
28883      if (names === void 0) {
28884        names = Object.keys(pluginStorage);
28885      }
28886
28887      var result;
28888      names.forEach(function (name) {
28889        var plugin = getPlugin(name);
28890
28891        if (plugin) {
28892          result = result || {};
28893          result[name] = plugin;
28894        }
28895      });
28896      return result;
28897    }
28898    /**
28899     * Gets a plugin's version, if available
28900     *
28901     * @param   {string} name
28902     *          The name of a plugin.
28903     *
28904     * @return {string}
28905     *          The plugin's version or an empty string.
28906     */
28907    ;
28908
28909    Plugin.getPluginVersion = function getPluginVersion(name) {
28910      var plugin = getPlugin(name);
28911      return plugin && plugin.VERSION || '';
28912    };
28913
28914    return Plugin;
28915  }();
28916  /**
28917   * Gets a plugin by name if it exists.
28918   *
28919   * @static
28920   * @method   getPlugin
28921   * @memberOf Plugin
28922   * @param    {string} name
28923   *           The name of a plugin.
28924   *
28925   * @returns  {Function|undefined}
28926   *           The plugin (or `undefined`).
28927   */
28928
28929
28930  Plugin.getPlugin = getPlugin;
28931  /**
28932   * The name of the base plugin class as it is registered.
28933   *
28934   * @type {string}
28935   */
28936
28937  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
28938  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
28939  /**
28940   * Documented in player.js
28941   *
28942   * @ignore
28943   */
28944
28945  Player.prototype.usingPlugin = function (name) {
28946    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
28947  };
28948  /**
28949   * Documented in player.js
28950   *
28951   * @ignore
28952   */
28953
28954
28955  Player.prototype.hasPlugin = function (name) {
28956    return !!pluginExists(name);
28957  };
28958  /**
28959   * Signals that a plugin is about to be set up on a player.
28960   *
28961   * @event    Player#beforepluginsetup
28962   * @type     {Plugin~PluginEventHash}
28963   */
28964
28965  /**
28966   * Signals that a plugin is about to be set up on a player - by name. The name
28967   * is the name of the plugin.
28968   *
28969   * @event    Player#beforepluginsetup:$name
28970   * @type     {Plugin~PluginEventHash}
28971   */
28972
28973  /**
28974   * Signals that a plugin has just been set up on a player.
28975   *
28976   * @event    Player#pluginsetup
28977   * @type     {Plugin~PluginEventHash}
28978   */
28979
28980  /**
28981   * Signals that a plugin has just been set up on a player - by name. The name
28982   * is the name of the plugin.
28983   *
28984   * @event    Player#pluginsetup:$name
28985   * @type     {Plugin~PluginEventHash}
28986   */
28987
28988  /**
28989   * @typedef  {Object} Plugin~PluginEventHash
28990   *
28991   * @property {string} instance
28992   *           For basic plugins, the return value of the plugin function. For
28993   *           advanced plugins, the plugin instance on which the event is fired.
28994   *
28995   * @property {string} name
28996   *           The name of the plugin.
28997   *
28998   * @property {string} plugin
28999   *           For basic plugins, the plugin function. For advanced plugins, the
29000   *           plugin class/constructor.
29001   */
29002
29003  function _inherits(subClass, superClass) {
29004    if (typeof superClass !== "function" && superClass !== null) {
29005      throw new TypeError("Super expression must either be null or a function");
29006    }
29007
29008    subClass.prototype = Object.create(superClass && superClass.prototype, {
29009      constructor: {
29010        value: subClass,
29011        writable: true,
29012        configurable: true
29013      }
29014    });
29015    if (superClass) setPrototypeOf(subClass, superClass);
29016  }
29017
29018  var inherits = _inherits;
29019
29020  /**
29021   * @file extend.js
29022   * @module extend
29023   */
29024  /**
29025   * Used to subclass an existing class by emulating ES subclassing using the
29026   * `extends` keyword.
29027   *
29028   * @function
29029   * @example
29030   * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
29031   *   myCustomMethod: function() {
29032   *     // Do things in my method.
29033   *   }
29034   * });
29035   *
29036   * @param    {Function} superClass
29037   *           The class to inherit from
29038   *
29039   * @param    {Object}   [subClassMethods={}]
29040   *           Methods of the new class
29041   *
29042   * @return   {Function}
29043   *           The new class with subClassMethods that inherited superClass.
29044   */
29045
29046  var extend = function extend(superClass, subClassMethods) {
29047    if (subClassMethods === void 0) {
29048      subClassMethods = {};
29049    }
29050
29051    var subClass = function subClass() {
29052      superClass.apply(this, arguments);
29053    };
29054
29055    var methods = {};
29056
29057    if (typeof subClassMethods === 'object') {
29058      if (subClassMethods.constructor !== Object.prototype.constructor) {
29059        subClass = subClassMethods.constructor;
29060      }
29061
29062      methods = subClassMethods;
29063    } else if (typeof subClassMethods === 'function') {
29064      subClass = subClassMethods;
29065    }
29066
29067    inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility.
29068
29069
29070    if (superClass) {
29071      subClass.super_ = superClass;
29072    } // Extend subObj's prototype with functions and other properties from props
29073
29074
29075    for (var name in methods) {
29076      if (methods.hasOwnProperty(name)) {
29077        subClass.prototype[name] = methods[name];
29078      }
29079    }
29080
29081    return subClass;
29082  };
29083
29084  /**
29085   * @file video.js
29086   * @module videojs
29087   */
29088  /**
29089   * Normalize an `id` value by trimming off a leading `#`
29090   *
29091   * @private
29092   * @param   {string} id
29093   *          A string, maybe with a leading `#`.
29094   *
29095   * @return {string}
29096   *          The string, without any leading `#`.
29097   */
29098
29099  var normalizeId = function normalizeId(id) {
29100    return id.indexOf('#') === 0 ? id.slice(1) : id;
29101  };
29102  /**
29103   * The `videojs()` function doubles as the main function for users to create a
29104   * {@link Player} instance as well as the main library namespace.
29105   *
29106   * It can also be used as a getter for a pre-existing {@link Player} instance.
29107   * However, we _strongly_ recommend using `videojs.getPlayer()` for this
29108   * purpose because it avoids any potential for unintended initialization.
29109   *
29110   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
29111   * of our JSDoc template, we cannot properly document this as both a function
29112   * and a namespace, so its function signature is documented here.
29113   *
29114   * #### Arguments
29115   * ##### id
29116   * string|Element, **required**
29117   *
29118   * Video element or video element ID.
29119   *
29120   * ##### options
29121   * Object, optional
29122   *
29123   * Options object for providing settings.
29124   * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
29125   *
29126   * ##### ready
29127   * {@link Component~ReadyCallback}, optional
29128   *
29129   * A function to be called when the {@link Player} and {@link Tech} are ready.
29130   *
29131   * #### Return Value
29132   *
29133   * The `videojs()` function returns a {@link Player} instance.
29134   *
29135   * @namespace
29136   *
29137   * @borrows AudioTrack as AudioTrack
29138   * @borrows Component.getComponent as getComponent
29139   * @borrows module:computed-style~computedStyle as computedStyle
29140   * @borrows module:events.on as on
29141   * @borrows module:events.one as one
29142   * @borrows module:events.off as off
29143   * @borrows module:events.trigger as trigger
29144   * @borrows EventTarget as EventTarget
29145   * @borrows module:extend~extend as extend
29146   * @borrows module:fn.bind as bind
29147   * @borrows module:format-time.formatTime as formatTime
29148   * @borrows module:format-time.resetFormatTime as resetFormatTime
29149   * @borrows module:format-time.setFormatTime as setFormatTime
29150   * @borrows module:merge-options.mergeOptions as mergeOptions
29151   * @borrows module:middleware.use as use
29152   * @borrows Player.players as players
29153   * @borrows Plugin.registerPlugin as registerPlugin
29154   * @borrows Plugin.deregisterPlugin as deregisterPlugin
29155   * @borrows Plugin.getPlugins as getPlugins
29156   * @borrows Plugin.getPlugin as getPlugin
29157   * @borrows Plugin.getPluginVersion as getPluginVersion
29158   * @borrows Tech.getTech as getTech
29159   * @borrows Tech.registerTech as registerTech
29160   * @borrows TextTrack as TextTrack
29161   * @borrows module:time-ranges.createTimeRanges as createTimeRange
29162   * @borrows module:time-ranges.createTimeRanges as createTimeRanges
29163   * @borrows module:url.isCrossOrigin as isCrossOrigin
29164   * @borrows module:url.parseUrl as parseUrl
29165   * @borrows VideoTrack as VideoTrack
29166   *
29167   * @param  {string|Element}
29167 id
29168   *         Video element or video element ID.
29169   *
29170   * @param  {Object} [options]
29171   *         Options object for providing settings.
29172   *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
29173   *
29174   * @param  {Component~ReadyCallback} [ready]
29175   *         A function to be called when the {@link Player} and {@link Tech} are
29176   *         ready.
29177   *
29178   * @return {Player}
29179   *         The `videojs()` function returns a {@link Player|Player} instance.
29180   */
29181
29182
29183  function videojs$1(id, options, ready) {
29184    var player = videojs$1.getPlayer(id);
29185
29186    if (player) {
29187      if (options) {
29188        log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
29189      }
29190
29191      if (ready) {
29192        player.ready(ready);
29193      }
29194
29195      return player;
29196    }
29197
29198    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
29199
29200    if (!isEl(el)) {
29201      throw new TypeError('The element or ID supplied is not valid. (videojs)');
29202    } // document.body.contains(el) will only check if el is contained within that one document.
29203    // This causes problems for elements in iframes.
29204    // Instead, use the element's ownerDocument instead of the global document.
29205    // This will make sure that the element is indeed in the dom of that document.
29206    // Additionally, check that the document in question has a default view.
29207    // If the document is no longer attached to the dom, the defaultView of the document will be null.
29208
29209
29210    if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
29211      log.warn('The element supplied is not included in the DOM');
29212    }
29213
29214    options = options || {};
29215    videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
29216      var opts = hookFunction(el, mergeOptions(options));
29217
29218      if (!isObject(opts) || Array.isArray(opts)) {
29219        log.error('please return an object in beforesetup hooks');
29220        return;
29221      }
29222
29223      options = mergeOptions(options, opts);
29224    }); // We get the current "Player" component here in case an integration has
29225    // replaced it with a custom player.
29226
29227    var PlayerComponent = Component.getComponent('Player');
29228    player = new PlayerComponent(el, options, ready);
29229    videojs$1.hooks('setup').forEach(function (hookFunction) {
29230      return hookFunction(player);
29231    });
29232    return player;
29233  }
29234  /**
29235   * An Object that contains lifecycle hooks as keys which point to an array
29236   * of functions that are run when a lifecycle is triggered
29237   *
29238   * @private
29239   */
29240
29241
29242  videojs$1.hooks_ = {};
29243  /**
29244   * Get a list of hooks for a specific lifecycle
29245   *
29246   * @param  {string} type
29247   *         the lifecyle to get hooks from
29248   *
29249   * @param  {Function|Function[]} [fn]
29250   *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
29251   *
29252   * @return {Array}
29253   *         an array of hooks, or an empty array if there are none.
29254   */
29255
29256  videojs$1.hooks = function (type, fn) {
29257    videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
29258
29259    if (fn) {
29260      videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
29261    }
29262
29263    return videojs$1.hooks_[type];
29264  };
29265  /**
29266   * Add a function hook to a specific videojs lifecycle.
29267   *
29268   * @param {string} type
29269   *        the lifecycle to hook the function to.
29270   *
29271   * @param {Function|Function[]}
29272   *        The function or array of functions to attach.
29273   */
29274
29275
29276  videojs$1.hook = function (type, fn) {
29277    videojs$1.hooks(type, fn);
29278  };
29279  /**
29280   * Add a function hook that will only run once to a specific videojs lifecycle.
29281   *
29282   * @param {string} type
29283   *        the lifecycle to hook the function to.
29284   *
29285   * @param {Function|Function[]}
29286   *        The function or array of functions to attach.
29287   */
29288
29289
29290  videojs$1.hookOnce = function (type, fn) {
29291    videojs$1.hooks(type, [].concat(fn).map(function (original) {
29292      var wrapper = function wrapper() {
29293        videojs$1.removeHook(type, wrapper);
29294        return original.apply(void 0, arguments);
29295      };
29296
29297      return wrapper;
29298    }));
29299  };
29300  /**
29301   * Remove a hook from a specific videojs lifecycle.
29302   *
29303   * @param  {string} type
29304   *         the lifecycle that the function hooked to
29305   *
29306   * @param  {Function} fn
29307   *         The hooked function to remove
29308   *
29309   * @return {boolean}
29310   *         The function that was removed or undef
29311   */
29312
29313
vendor: 8,807 bytes, lines 29314-29610
29314  videojs$1.removeHook = function (type, fn) {
29315    var index = videojs$1.hooks(type).indexOf(fn);
29316
29317    if (index <= -1) {
29318      return false;
29319    }
29320
29321    videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
29322    videojs$1.hooks_[type].splice(index, 1);
29323    return true;
29324  }; // Add default styles
29325
29326
29327  if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
29328    var style = $('.vjs-styles-defaults');
29329
29330    if (!style) {
29331      style = createStyleElement('vjs-styles-defaults');
29332      var head = $('head');
29333
29334      if (head) {
29335        head.insertBefore(style, head.firstChild);
29336      }
29337
29338      setTextContent(style, "\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ");
29339    }
29340  } // Run Auto-load players
29341  // You have to wait at least once in case this script is loaded after your
29342  // video in the DOM (weird behavior only with minified version)
29343
29344
29345  autoSetupTimeout(1, videojs$1);
29346  /**
29347   * Current Video.js version. Follows [semantic versioning](https://semver.org/).
29348   *
29349   * @type {string}
29350   */
29351
29352  videojs$1.VERSION = version;
29353  /**
29354   * The global options object. These are the settings that take effect
29355   * if no overrides are specified when the player is created.
29356   *
29357   * @type {Object}
29358   */
29359
29360  videojs$1.options = Player.prototype.options_;
29361  /**
29362   * Get an object with the currently created players, keyed by player ID
29363   *
29364   * @return {Object}
29365   *         The created players
29366   */
29367
29368  videojs$1.getPlayers = function () {
29369    return Player.players;
29370  };
29371  /**
29372   * Get a single player based on an ID or DOM element.
29373   *
29374   * This is useful if you want to check if an element or ID has an associated
29375   * Video.js player, but not create one if it doesn't.
29376   *
29377   * @param   {string|Element} id
29378   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
29379   *          or a string matching the `id` of such an element.
29380   *
29381   * @return {Player|undefined}
29382   *          A player instance or `undefined` if there is no player instance
29383   *          matching the argument.
29384   */
29385
29386
29387  videojs$1.getPlayer = function (id) {
29388    var players = Player.players;
29389    var tag;
29390
29391    if (typeof id === 'string') {
29392      var nId = normalizeId(id);
29393      var player = players[nId];
29394
29395      if (player) {
29396        return player;
29397      }
29398
29399      tag = $('#' + nId);
29400    } else {
29401      tag = id;
29402    }
29403
29404    if (isEl(tag)) {
29405      var _tag = tag,
29406          _player = _tag.player,
29407          playerId = _tag.playerId; // Element may have a `player` property referring to an already created
29408      // player instance. If so, return that.
29409
29410      if (_player || players[playerId]) {
29411        return _player || players[playerId];
29412      }
29413    }
29414  };
29415  /**
29416   * Returns an array of all current players.
29417   *
29418   * @return {Array}
29419   *         An array of all players. The array will be in the order that
29420   *         `Object.keys` provides, which could potentially vary between
29421   *         JavaScript engines.
29422   *
29423   */
29424
29425
29426  videojs$1.getAllPlayers = function () {
29427    return (// Disposed players leave a key with a `null` value, so we need to make sure
29428      // we filter those out.
29429      Object.keys(Player.players).map(function (k) {
29430        return Player.players[k];
29431      }).filter(Boolean)
29432    );
29433  };
29434
29435  videojs$1.players = Player.players;
29436  videojs$1.getComponent = Component.getComponent;
29437  /**
29438   * Register a component so it can referred to by name. Used when adding to other
29439   * components, either through addChild `component.addChild('myComponent')` or through
29440   * default children options  `{ children: ['myComponent'] }`.
29441   *
29442   * > NOTE: You could also just initialize the component before adding.
29443   * `component.addChild(new MyComponent());`
29444   *
29445   * @param {string} name
29446   *        The class name of the component
29447   *
29448   * @param {Component} comp
29449   *        The component class
29450   *
29451   * @return {Component}
29452   *         The newly registered component
29453   */
29454
29455  videojs$1.registerComponent = function (name, comp) {
29456    if (Tech.isTech(comp)) {
29457      log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
29458    }
29459
29460    Component.registerComponent.call(Component, name, comp);
29461  };
29462
29463  videojs$1.getTech = Tech.getTech;
29464  videojs$1.registerTech = Tech.registerTech;
29465  videojs$1.use = use;
29466  /**
29467   * An object that can be returned by a middleware to signify
29468   * that the middleware is being terminated.
29469   *
29470   * @type {object}
29471   * @property {object} middleware.TERMINATOR
29472   */
29473
29474  Object.defineProperty(videojs$1, 'middleware', {
29475    value: {},
29476    writeable: false,
29477    enumerable: true
29478  });
29479  Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
29480    value: TERMINATOR,
29481    writeable: false,
29482    enumerable: true
29483  });
29484  /**
29485   * A reference to the {@link module:browser|browser utility module} as an object.
29486   *
29487   * @type {Object}
29488   * @see  {@link module:browser|browser}
29489   */
29490
29491  videojs$1.browser = browser;
29492  /**
29493   * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
29494   * included for backward-compatibility with 4.x.
29495   *
29496   * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
29497   * @type {boolean}
29498   */
29499
29500  videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
29501  videojs$1.extend = extend;
29502  videojs$1.mergeOptions = mergeOptions;
29503  videojs$1.bind = bind;
29504  videojs$1.registerPlugin = Plugin.registerPlugin;
29505  videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
29506  /**
29507   * Deprecated method to register a plugin with Video.js
29508   *
29509   * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
29510   *
29511   * @param {string} name
29512   *        The plugin name
29513   *
29514   * @param {Plugin|Function} plugin
29515   *         The plugin sub-class or function
29516   */
29517
29518  videojs$1.plugin = function (name, plugin) {
29519    log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
29520    return Plugin.registerPlugin(name, plugin);
29521  };
29522
29523  videojs$1.getPlugins = Plugin.getPlugins;
29524  videojs$1.getPlugin = Plugin.getPlugin;
29525  videojs$1.getPluginVersion = Plugin.getPluginVersion;
29526  /**
29527   * Adding languages so that they're available to all players.
29528   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
29529   *
29530   * @param {string} code
29531   *        The language code or dictionary property
29532   *
29533   * @param {Object} data
29534   *        The data values to be translated
29535   *
29536   * @return {Object}
29537   *         The resulting language dictionary object
29538   */
29539
29540  videojs$1.addLanguage = function (code, data) {
29541    var _mergeOptions;
29542
29543    code = ('' + code).toLowerCase();
29544    videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
29545    return videojs$1.options.languages[code];
29546  };
29547  /**
29548   * A reference to the {@link module:log|log utility module} as an object.
29549   *
29550   * @type {Function}
29551   * @see  {@link module:log|log}
29552   */
29553
29554
29555  videojs$1.log = log;
29556  videojs$1.createLogger = createLogger$1;
29557  videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
29558  videojs$1.formatTime = formatTime;
29559  videojs$1.setFormatTime = setFormatTime;
29560  videojs$1.resetFormatTime = resetFormatTime;
29561  videojs$1.parseUrl = parseUrl;
29562  videojs$1.isCrossOrigin = isCrossOrigin;
29563  videojs$1.EventTarget = EventTarget;
29564  videojs$1.on = on;
29565  videojs$1.one = one;
29566  videojs$1.off = off;
29567  videojs$1.trigger = trigger;
29568  /**
29569   * A cross-browser XMLHttpRequest wrapper.
29570   *
29571   * @function
29572   * @param    {Object} options
29573   *           Settings for the request.
29574   *
29575   * @return   {XMLHttpRequest|XDomainRequest}
29576   *           The request object.
29577   *
29578   * @see      https://github.com/Raynos/xhr
29579   */
29580
29581  videojs$1.xhr = xhr;
29582  videojs$1.TextTrack = TextTrack;
29583  videojs$1.AudioTrack = AudioTrack;
29584  videojs$1.VideoTrack = VideoTrack;
29585  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
29586    videojs$1[k] = function () {
29587      log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
29588      return Dom[k].apply(null, arguments);
29589    };
29590  });
29591  videojs$1.computedStyle = computedStyle;
29592  /**
29593   * A reference to the {@link module:dom|DOM utility module} as an object.
29594   *
29595   * @type {Object}
29596   * @see  {@link module:dom|dom}
29597   */
29598
29599  videojs$1.dom = Dom;
29600  /**
29601   * A reference to the {@link module:url|URL utility module} as an object.
29602   *
29603   * @type {Object}
29604   * @see  {@link module:url|url}
29605   */
29606
29607  videojs$1.url = Url;
29608  videojs$1.defineLazyProperty = defineLazyProperty;
29609
29610  var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,642 bytes, lines 29610-29717
29610) {
29611    // see https://tools.ietf.org/html/rfc1808
29612
29613    /* jshint ignore:start */
29614    (function (root) {
29615      /* jshint ignore:end */
29616      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
29617      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
29618      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
29619      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
29620      var URLToolkit = {
29621        // jshint ignore:line
29622        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
29623        // E.g
29624        // With opts.alwaysNormalize = false (default, spec compliant)
29625        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
29626        // With opts.alwaysNormalize = true (not spec compliant)
29627        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
29628        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
29629          opts = opts || {}; // remove any remaining space and CRLF
29630
29631          baseURL = baseURL.trim();
29632          relativeURL = relativeURL.trim();
29633
29634          if (!relativeURL) {
29635            // 2a) If the embedded URL is entirely empty, it inherits the
29636            // entire base URL (i.e., is set equal to the base URL)
29637            // and we are done.
29638            if (!opts.alwaysNormalize) {
29639              return baseURL;
29640            }
29641
29642            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
29643
29644            if (!basePartsForNormalise) {
29645              throw new Error('Error trying to parse base URL.');
29646            }
29647
29648            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
29649            return URLToolkit.buildURLFromParts(basePartsForNormalise);
29650          }
29651
29652          var relativeParts = URLToolkit.parseURL(relativeURL);
29653
29654          if (!relativeParts) {
29655            throw new Error('Error trying to parse relative URL.');
29656          }
29657
29658          if (relativeParts.scheme) {
29659            // 2b) If the embedded URL starts with a scheme name, it is
29660            // interpreted as an absolute URL and we are done.
29661            if (!opts.alwaysNormalize) {
29662              return relativeURL;
29663            }
29664
29665            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
29666            return URLToolkit.buildURLFromParts(relativeParts);
29667          }
29668
29669          var baseParts = URLToolkit.parseURL(baseURL);
29670
29671          if (!baseParts) {
29672            throw new Error('Error trying to parse base URL.');
29673          }
29674
29675          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
29676            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
29677            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
29678            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
29679            baseParts.netLoc = pathParts[1];
29680            baseParts.path = pathParts[2];
29681          }
29682
29683          if (baseParts.netLoc && !baseParts.path) {
29684            baseParts.path = '/';
29685          }
29686
29687          var builtParts = {
29688            // 2c) Otherwise, the embedded URL inherits the scheme of
29689            // the base URL.
29690            scheme: baseParts.scheme,
29691            netLoc: relativeParts.netLoc,
29692            path: null,
29693            params: relativeParts.params,
29694            query: relativeParts.query,
29695            fragment: relativeParts.fragment
29696          };
29697
29698          if (!relativeParts.netLoc) {
29699            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
29700            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
29701            // (if any) of the base URL.
29702            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
29703            // path is not relative and we skip to Step 7.
29704
29705            if (relativeParts.path[0] !== '/') {
29706              if (!relativeParts.path) {
29707                // 5) If the embedded URL path is empty (and not preceded by a
29708                // slash), then the embedded URL inherits the base URL path
29709                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
29710                // step 7; otherwise, it inherits the <params> of the base
29711                // URL (if any) and
29712
29713                if (!relativeParts.params) {
29714                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
29715                  // step 7; otherwise, it inherits the <query> of the base
29716                  // URL (if any) and we skip to step 7.
29717
29718                  if (!relativeParts.query) {
29719                    builtParts.query = baseParts.query;
29720                  }
29721                }
29722              } else {
29723                // 6) The last segment of the base URL's path (anything
29724                // following the rightmost slash "/", or the entire path if no
29725                // slash is present) is removed and the embedded URL's path is
29726                // appended in its place.
29727                var baseURLPath = baseParts.path;
29728                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
29729                builtParts.path = URLToolkit.normalizePath(newPath);
29730              }
29731            }
29732          }
29733
29734          if (builtParts.path === null) {
29735            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
29736          }
29737
29738          return URLToolkit.buildURLFromParts(builtParts);
29739        },
29740        parseURL: function parseURL(url) {
29741          var parts = URL_REGEX.exec(url);
29742
29743          if (!parts) {
29744            return null;
29745          }
29746
29747          return {
29748            scheme: parts[1] || '',
29749            netLoc: parts[2] || '',
29750            path: parts[3] || '',
29751            params: parts[4] || '',
29752            query: parts[5] || '',
29753            fragment: parts[6] || ''
29754          };
29755        },
29756        normalizePath: function normalizePath(path) {
29757          // The following operations are
29758          // then applied, in order, to the new path:
29759          // 6a) All occurrences of "./", where "." is a complete path
29760          // segment, are removed.
29761          // 6b) If the path ends with "." as a complete path segment,
29762          // that "." is removed.
29763          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
29764          // complete path segment not equal to "..", are removed.
29765          // Removal of these path segments is performed iteratively,
29766          // removing the leftmost matching pattern on each iteration,
29767          // until no matching pattern remains.
29768          // 6d) If the path ends with "<segment>/..", where <segment> is a
29769          // complete path segment not equal to "..", that
29770          // "<segment>/.." is removed.
29771
29772          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
29773
29774
29775          return path.split('').reverse().join('');
29776        },
29777        buildURLFromParts: function buildURLFromParts(parts) {
29778          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
29779        }
29780      };
29781      /* jshint ignore:start */
29782
29783      module.exports = URLToolkit;
29784    })();
29785    /* jshint ignore:end */
29786
29787  });
29788
29789  function _interopDefaultLegacy(e) {
29790    return e && typeof e === 'object' && 'default' in e ? e : {
29791      'default': e
29792    };
29793  }
29794
29795  var URLToolkit__default = /*#__PURE__*/_interopDefaultLegacy(urlToolkit);
29796
29797  var window__default = /*#__PURE__*/_interopDefaultLegacy(window$1);
29798
29799  var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
29800    // return early if we don't need to resolve
29801    if (/^[a-z]+:/i.test(relativeUrl)) {
29802      return relativeUrl;
29803    } // if the base URL is relative then combine with the current location
29804
29805
29806    if (!/\/\//i.test(baseUrl)) {
29807      baseUrl = URLToolkit__default['default'].buildAbsoluteURL(window__default['default'].location && window__default['default'].location.href || '', baseUrl);
29808    }
29809
29810    return URLToolkit__default['default'].buildAbsoluteURL(baseUrl, relativeUrl);
29811  };
29812
29813  var resolveUrl_1 = resolveUrl;
29814
29815  /*! @name @videojs/vhs-utils @version 2.2.1 @license MIT */
29816  /**
29817   * @file stream.js
29818   */
29819
29820  /**
29821   * A lightweight readable stream implemention that handles event dispatching.
29822   *
29823   * @class Stream
29824   */
29825
29826  var Stream = /*#__PURE__*/function () {
29827    function Stream() {
29828      this.listeners = {};
29829    }
29830    /**
29831     * Add a listener for a specified event type.
29832     *
29833     * @param {string} type the event name
29834     * @param {Function} listener the callback to be invoked when an event of
29835     * the specified type occurs
29836     */
29837
29838
29839    var _proto = Stream.prototype;
29840
29841    _proto.on = function on(type, listener) {
29842      if (!this.listeners[type]) {
29843        this.listeners[type] = [];
29844      }
29845
29846      this.listeners[type].push(listener);
29847    }
29848    /**
29849     * Remove a listener for a specified event type.
29850     *
29851     * @param {string} type the event name
29852     * @param {Function} listener  a function previously registered for this
29853     * type of event through `on`
29854     * @return {boolean} if we could turn it off or not
29855     */
29856    ;
29857
29858    _proto.off = function off(type, listener) {
29859      if (!this.listeners[type]) {
29860        return false;
vendor: 42,620 bytes, lines 29861-31243
29861      }
29862
29863      var index = this.listeners[type].indexOf(listener); // TODO: which is better?
29864      // In Video.js we slice listener functions
29865      // on trigger so that it does not mess up the order
29866      // while we loop through.
29867      //
29868      // Here we slice on off so that the loop in trigger
29869      // can continue using it's old reference to loop without
29870      // messing up the order.
29871
29872      this.listeners[type] = this.listeners[type].slice(0);
29873      this.listeners[type].splice(index, 1);
29874      return index > -1;
29875    }
29876    /**
29877     * Trigger an event of the specified type on this stream. Any additional
29878     * arguments to this function are passed as parameters to event listeners.
29879     *
29880     * @param {string} type the event name
29881     */
29882    ;
29883
29884    _proto.trigger = function trigger(type) {
29885      var callbacks = this.listeners[type];
29886
29887      if (!callbacks) {
29888        return;
29889      } // Slicing the arguments on every invocation of this method
29890      // can add a significant amount of overhead. Avoid the
29891      // intermediate object creation for the common case of a
29892      // single callback argument
29893
29894
29895      if (arguments.length === 2) {
29896        var length = callbacks.length;
29897
29898        for (var i = 0; i < length; ++i) {
29899          callbacks[i].call(this, arguments[1]);
29900        }
29901      } else {
29902        var args = Array.prototype.slice.call(arguments, 1);
29903        var _length = callbacks.length;
29904
29905        for (var _i = 0; _i < _length; ++_i) {
29906          callbacks[_i].apply(this, args);
29907        }
29908      }
29909    }
29910    /**
29911     * Destroys the stream and cleans up.
29912     */
29913    ;
29914
29915    _proto.dispose = function dispose() {
29916      this.listeners = {};
29917    }
29918    /**
29919     * Forwards all `data` events on this stream to the destination stream. The
29920     * destination stream should provide a method `push` to receive the data
29921     * events as they arrive.
29922     *
29923     * @param {Stream} destination the stream that will receive all `data` events
29924     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
29925     */
29926    ;
29927
29928    _proto.pipe = function pipe(destination) {
29929      this.on('data', function (data) {
29930        destination.push(data);
29931      });
29932    };
29933
29934    return Stream;
29935  }();
29936
29937  var stream = Stream;
29938
29939  function _interopDefaultLegacy$1(e) {
29940    return e && typeof e === 'object' && 'default' in e ? e : {
29941      'default': e
29942    };
29943  }
29944
29945  var window__default$1 = /*#__PURE__*/_interopDefaultLegacy$1(window$1);
29946
29947  var atob = function atob(s) {
29948    return window__default$1['default'].atob ? window__default$1['default'].atob(s) : Buffer.from(s, 'base64').toString('binary');
29949  };
29950
29951  function decodeB64ToUint8Array(b64Text) {
29952    var decodedString = atob(b64Text);
29953    var array = new Uint8Array(decodedString.length);
29954
29955    for (var i = 0; i < decodedString.length; i++) {
29956      array[i] = decodedString.charCodeAt(i);
29957    }
29958
29959    return array;
29960  }
29961
29962  var decodeB64ToUint8Array_1 = decodeB64ToUint8Array;
29963
29964  /*! @name m3u8-parser @version 4.5.0 @license Apache-2.0 */
29965  /**
29966   * A stream that buffers string input and generates a `data` event for each
29967   * line.
29968   *
29969   * @class LineStream
29970   * @extends Stream
29971   */
29972
29973  var LineStream = /*#__PURE__*/function (_Stream) {
29974    inheritsLoose(LineStream, _Stream);
29975
29976    function LineStream() {
29977      var _this;
29978
29979      _this = _Stream.call(this) || this;
29980      _this.buffer = '';
29981      return _this;
29982    }
29983    /**
29984     * Add new data to be parsed.
29985     *
29986     * @param {string} data the text to process
29987     */
29988
29989
29990    var _proto = LineStream.prototype;
29991
29992    _proto.push = function push(data) {
29993      var nextNewline;
29994      this.buffer += data;
29995      nextNewline = this.buffer.indexOf('\n');
29996
29997      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
29998        this.trigger('data', this.buffer.substring(0, nextNewline));
29999        this.buffer = this.buffer.substring(nextNewline + 1);
30000      }
30001    };
30002
30003    return LineStream;
30004  }(stream);
30005  /**
30006   * "forgiving" attribute list psuedo-grammar:
30007   * attributes -> keyvalue (',' keyvalue)*
30008   * keyvalue   -> key '=' value
30009   * key        -> [^=]*
30010   * value      -> '"' [^"]* '"' | [^,]*
30011   */
30012
30013
30014  var attributeSeparator = function attributeSeparator() {
30015    var key = '[^=]*';
30016    var value = '"[^"]*"|[^,]*';
30017    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
30018    return new RegExp('(?:^|,)(' + keyvalue + ')');
30019  };
30020  /**
30021   * Parse attributes from a line given the separator
30022   *
30023   * @param {string} attributes the attribute line to parse
30024   */
30025
30026
30027  var parseAttributes = function parseAttributes(attributes) {
30028    // split the string using attributes as the separator
30029    var attrs = attributes.split(attributeSeparator());
30030    var result = {};
30031    var i = attrs.length;
30032    var attr;
30033
30034    while (i--) {
30035      // filter out unmatched portions of the string
30036      if (attrs[i] === '') {
30037        continue;
30038      } // split the key and value
30039
30040
30041      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
30042
30043      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
30044      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
30045      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
30046      result[attr[0]] = attr[1];
30047    }
30048
30049    return result;
30050  };
30051  /**
30052   * A line-level M3U8 parser event stream. It expects to receive input one
30053   * line at a time and performs a context-free parse of its contents. A stream
30054   * interpretation of a manifest can be useful if the manifest is expected to
30055   * be too large to fit comfortably into memory or the entirety of the input
30056   * is not immediately available. Otherwise, it's probably much easier to work
30057   * with a regular `Parser` object.
30058   *
30059   * Produces `data` events with an object that captures the parser's
30060   * interpretation of the input. That object has a property `tag` that is one
30061   * of `uri`, `comment`, or `tag`. URIs only have a single additional
30062   * property, `line`, which captures the entirety of the input without
30063   * interpretation. Comments similarly have a single additional property
30064   * `text` which is the input without the leading `#`.
30065   *
30066   * Tags always have a property `tagType` which is the lower-cased version of
30067   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
30068   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
30069   * tags are given the tag type `unknown` and a single additional property
30070   * `data` with the remainder of the input.
30071   *
30072   * @class ParseStream
30073   * @extends Stream
30074   */
30075
30076
30077  var ParseStream = /*#__PURE__*/function (_Stream) {
30078    inheritsLoose(ParseStream, _Stream);
30079
30080    function ParseStream() {
30081      var _this;
30082
30083      _this = _Stream.call(this) || this;
30084      _this.customParsers = [];
30085      _this.tagMappers = [];
30086      return _this;
30087    }
30088    /**
30089     * Parses an additional line of input.
30090     *
30091     * @param {string} line a single line of an M3U8 file to parse
30092     */
30093
30094
30095    var _proto = ParseStream.prototype;
30096
30097    _proto.push = function push(line) {
30098      var _this2 = this;
30099
30100      var match;
30101      var event; // strip whitespace
30102
30103      line = line.trim();
30104
30105      if (line.length === 0) {
30106        // ignore empty lines
30107        return;
30108      } // URIs
30109
30110
30111      if (line[0] !== '#') {
30112        this.trigger('data', {
30113          type: 'uri',
30114          uri: line
30115        });
30116        return;
30117      } // map tags
30118
30119
30120      var newLines = this.tagMappers.reduce(function (acc, mapper) {
30121        var mappedLine = mapper(line); // skip if unchanged
30122
30123        if (mappedLine === line) {
30124          return acc;
30125        }
30126
30127        return acc.concat([mappedLine]);
30128      }, [line]);
30129      newLines.forEach(function (newLine) {
30130        for (var i = 0; i < _this2.customParsers.length; i++) {
30131          if (_this2.customParsers[i].call(_this2, newLine)) {
30132            return;
30133          }
30134        } // Comments
30135
30136
30137        if (newLine.indexOf('#EXT') !== 0) {
30138          _this2.trigger('data', {
30139            type: 'comment',
30140            text: newLine.slice(1)
30141          });
30142
30143          return;
30144        } // strip off any carriage returns here so the regex matching
30145        // doesn't have to account for them.
30146
30147
30148        newLine = newLine.replace('\r', ''); // Tags
30149
30150        match = /^#EXTM3U/.exec(newLine);
30151
30152        if (match) {
30153          _this2.trigger('data', {
30154            type: 'tag',
30155            tagType: 'm3u'
30156          });
30157
30158          return;
30159        }
30160
30161        match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
30162
30163        if (match) {
30164          event = {
30165            type: 'tag',
30166            tagType: 'inf'
30167          };
30168
30169          if (match[1]) {
30170            event.duration = parseFloat(match[1]);
30171          }
30172
30173          if (match[2]) {
30174            event.title = match[2];
30175          }
30176
30177          _this2.trigger('data', event);
30178
30179          return;
30180        }
30181
30182        match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
30183
30184        if (match) {
30185          event = {
30186            type: 'tag',
30187            tagType: 'targetduration'
30188          };
30189
30190          if (match[1]) {
30191            event.duration = parseInt(match[1], 10);
30192          }
30193
30194          _this2.trigger('data', event);
30195
30196          return;
30197        }
30198
30199        match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
30200
30201        if (match) {
30202          event = {
30203            type: 'tag',
30204            tagType: 'totalduration'
30205          };
30206
30207          if (match[1]) {
30208            event.duration = parseInt(match[1], 10);
30209          }
30210
30211          _this2.trigger('data', event);
30212
30213          return;
30214        }
30215
30216        match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
30217
30218        if (match) {
30219          event = {
30220            type: 'tag',
30221            tagType: 'version'
30222          };
30223
30224          if (match[1]) {
30225            event.version = parseInt(match[1], 10);
30226          }
30227
30228          _this2.trigger('data', event);
30229
30230          return;
30231        }
30232
30233        match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
30234
30235        if (match) {
30236          event = {
30237            type: 'tag',
30238            tagType: 'media-sequence'
30239          };
30240
30241          if (match[1]) {
30242            event.number = parseInt(match[1], 10);
30243          }
30244
30245          _this2.trigger('data', event);
30246
30247          return;
30248        }
30249
30250        match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
30251
30252        if (match) {
30253          event = {
30254            type: 'tag',
30255            tagType: 'discontinuity-sequence'
30256          };
30257
30258          if (match[1]) {
30259            event.number = parseInt(match[1], 10);
30260          }
30261
30262          _this2.trigger('data', event);
30263
30264          return;
30265        }
30266
30267        match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
30268
30269        if (match) {
30270          event = {
30271            type: 'tag',
30272            tagType: 'playlist-type'
30273          };
30274
30275          if (match[1]) {
30276            event.playlistType = match[1];
30277          }
30278
30279          _this2.trigger('data', event);
30280
30281          return;
30282        }
30283
30284        match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
30285
30286        if (match) {
30287          event = {
30288            type: 'tag',
30289            tagType: 'byterange'
30290          };
30291
30292          if (match[1]) {
30293            event.length = parseInt(match[1], 10);
30294          }
30295
30296          if (match[2]) {
30297            event.offset = parseInt(match[2], 10);
30298          }
30299
30300          _this2.trigger('data', event);
30301
30302          return;
30303        }
30304
30305        match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
30306
30307        if (match) {
30308          event = {
30309            type: 'tag',
30310            tagType: 'allow-cache'
30311          };
30312
30313          if (match[1]) {
30314            event.allowed = !/NO/.test(match[1]);
30315          }
30316
30317          _this2.trigger('data', event);
30318
30319          return;
30320        }
30321
30322        match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
30323
30324        if (match) {
30325          event = {
30326            type: 'tag',
30327            tagType: 'map'
30328          };
30329
30330          if (match[1]) {
30331            var attributes = parseAttributes(match[1]);
30332
30333            if (attributes.URI) {
30334              event.uri = attributes.URI;
30335            }
30336
30337            if (attributes.BYTERANGE) {
30338              var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
30339                  length = _attributes$BYTERANGE[0],
30340                  offset = _attributes$BYTERANGE[1];
30341
30342              event.byterange = {};
30343
30344              if (length) {
30345                event.byterange.length = parseInt(length, 10);
30346              }
30347
30348              if (offset) {
30349                event.byterange.offset = parseInt(offset, 10);
30350              }
30351            }
30352          }
30353
30354          _this2.trigger('data', event);
30355
30356          return;
30357        }
30358
30359        match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
30360
30361        if (match) {
30362          event = {
30363            type: 'tag',
30364            tagType: 'stream-inf'
30365          };
30366
30367          if (match[1]) {
30368            event.attributes = parseAttributes(match[1]);
30369
30370            if (event.attributes.RESOLUTION) {
30371              var split = event.attributes.RESOLUTION.split('x');
30372              var resolution = {};
30373
30374              if (split[0]) {
30375                resolution.width = parseInt(split[0], 10);
30376              }
30377
30378              if (split[1]) {
30379                resolution.height = parseInt(split[1], 10);
30380              }
30381
30382              event.attributes.RESOLUTION = resolution;
30383            }
30384
30385            if (event.attributes.BANDWIDTH) {
30386              event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
30387            }
30388
30389            if (event.attributes['PROGRAM-ID']) {
30390              event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
30391            }
30392          }
30393
30394          _this2.trigger('data', event);
30395
30396          return;
30397        }
30398
30399        match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
30400
30401        if (match) {
30402          event = {
30403            type: 'tag',
30404            tagType: 'media'
30405          };
30406
30407          if (match[1]) {
30408            event.attributes = parseAttributes(match[1]);
30409          }
30410
30411          _this2.trigger('data', event);
30412
30413          return;
30414        }
30415
30416        match = /^#EXT-X-ENDLIST/.exec(newLine);
30417
30418        if (match) {
30419          _this2.trigger('data', {
30420            type: 'tag',
30421            tagType: 'endlist'
30422          });
30423
30424          return;
30425        }
30426
30427        match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
30428
30429        if (match) {
30430          _this2.trigger('data', {
30431            type: 'tag',
30432            tagType: 'discontinuity'
30433          });
30434
30435          return;
30436        }
30437
30438        match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
30439
30440        if (match) {
30441          event = {
30442            type: 'tag',
30443            tagType: 'program-date-time'
30444          };
30445
30446          if (match[1]) {
30447            event.dateTimeString = match[1];
30448            event.dateTimeObject = new Date(match[1]);
30449          }
30450
30451          _this2.trigger('data', event);
30452
30453          return;
30454        }
30455
30456        match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
30457
30458        if (match) {
30459          event = {
30460            type: 'tag',
30461            tagType: 'key'
30462          };
30463
30464          if (match[1]) {
30465            event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
30466
30467            if (event.attributes.IV) {
30468              if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
30469                event.attributes.IV = event.attributes.IV.substring(2);
30470              }
30471
30472              event.attributes.IV = event.attributes.IV.match(/.{8}/g);
30473              event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
30474              event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
30475              event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
30476              event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
30477              event.attributes.IV = new Uint32Array(event.attributes.IV);
30478            }
30479          }
30480
30481          _this2.trigger('data', event);
30482
30483          return;
30484        }
30485
30486        match = /^#EXT-X-START:?(.*)$/.exec(newLine);
30487
30488        if (match) {
30489          event = {
30490            type: 'tag',
30491            tagType: 'start'
30492          };
30493
30494          if (match[1]) {
30495            event.attributes = parseAttributes(match[1]);
30496            event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
30497            event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
30498          }
30499
30500          _this2.trigger('data', event);
30501
30502          return;
30503        }
30504
30505        match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
30506
30507        if (match) {
30508          event = {
30509            type: 'tag',
30510            tagType: 'cue-out-cont'
30511          };
30512
30513          if (match[1]) {
30514            event.data = match[1];
30515          } else {
30516            event.data = '';
30517          }
30518
30519          _this2.trigger('data', event);
30520
30521          return;
30522        }
30523
30524        match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
30525
30526        if (match) {
30527          event = {
30528            type: 'tag',
30529            tagType: 'cue-out'
30530          };
30531
30532          if (match[1]) {
30533            event.data = match[1];
30534          } else {
30535            event.data = '';
30536          }
30537
30538          _this2.trigger('data', event);
30539
30540          return;
30541        }
30542
30543        match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
30544
30545        if (match) {
30546          event = {
30547            type: 'tag',
30548            tagType: 'cue-in'
30549          };
30550
30551          if (match[1]) {
30552            event.data = match[1];
30553          } else {
30554            event.data = '';
30555          }
30556
30557          _this2.trigger('data', event);
30558
30559          return;
30560        } // unknown tag type
30561
30562
30563        _this2.trigger('data', {
30564          type: 'tag',
30565          data: newLine.slice(4)
30566        });
30567      });
30568    }
30569    /**
30570     * Add a parser for custom headers
30571     *
30572     * @param {Object}   options              a map of options for the added parser
30573     * @param {RegExp}   options.expression   a regular expression to match the custom header
30574     * @param {string}   options.customType   the custom type to register to the output
30575     * @param {Function} [options.dataParser] function to parse the line into an object
30576     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
30577     */
30578    ;
30579
30580    _proto.addParser = function addParser(_ref) {
30581      var _this3 = this;
30582
30583      var expression = _ref.expression,
30584          customType = _ref.customType,
30585          dataParser = _ref.dataParser,
30586          segment = _ref.segment;
30587
30588      if (typeof dataParser !== 'function') {
30589        dataParser = function dataParser(line) {
30590          return line;
30591        };
30592      }
30593
30594      this.customParsers.push(function (line) {
30595        var match = expression.exec(line);
30596
30597        if (match) {
30598          _this3.trigger('data', {
30599            type: 'custom',
30600            data: dataParser(line),
30601            customType: customType,
30602            segment: segment
30603          });
30604
30605          return true;
30606        }
30607      });
30608    }
30609    /**
30610     * Add a custom header mapper
30611     *
30612     * @param {Object}   options
30613     * @param {RegExp}   options.expression   a regular expression to match the custom header
30614     * @param {Function} options.map          function to translate tag into a different tag
30615     */
30616    ;
30617
30618    _proto.addTagMapper = function addTagMapper(_ref2) {
30619      var expression = _ref2.expression,
30620          map = _ref2.map;
30621
30622      var mapFn = function mapFn(line) {
30623        if (expression.test(line)) {
30624          return map(line);
30625        }
30626
30627        return line;
30628      };
30629
30630      this.tagMappers.push(mapFn);
30631    };
30632
30633    return ParseStream;
30634  }(stream);
30635  /**
30636   * A parser for M3U8 files. The current interpretation of the input is
30637   * exposed as a property `manifest` on parser objects. It's just two lines to
30638   * create and parse a manifest once you have the contents available as a string:
30639   *
30640   * ```js
30641   * var parser = new m3u8.Parser();
30642   * parser.push(xhr.responseText);
30643   * ```
30644   *
30645   * New input can later be applied to update the manifest object by calling
30646   * `push` again.
30647   *
30648   * The parser attempts to create a usable manifest object even if the
30649   * underlying input is somewhat nonsensical. It emits `info` and `warning`
30650   * events during the parse if it encounters input that seems invalid or
30651   * requires some property of the manifest object to be defaulted.
30652   *
30653   * @class Parser
30654   * @extends Stream
30655   */
30656
30657
30658  var Parser = /*#__PURE__*/function (_Stream) {
30659    inheritsLoose(Parser, _Stream);
30660
30661    function Parser() {
30662      var _this;
30663
30664      _this = _Stream.call(this) || this;
30665      _this.lineStream = new LineStream();
30666      _this.parseStream = new ParseStream();
30667
30668      _this.lineStream.pipe(_this.parseStream);
30669      /* eslint-disable consistent-this */
30670
30671
30672      var self = assertThisInitialized(_this);
30673      /* eslint-enable consistent-this */
30674
30675
30676      var uris = [];
30677      var currentUri = {}; // if specified, the active EXT-X-MAP definition
30678
30679      var currentMap; // if specified, the active decryption key
30680
30681      var _key;
30682
30683      var noop = function noop() {};
30684
30685      var defaultMediaGroups = {
30686        'AUDIO': {},
30687        'VIDEO': {},
30688        'CLOSED-CAPTIONS': {},
30689        'SUBTITLES': {}
30690      }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
30691      // used in MPDs with Widevine encrypted streams.
30692
30693      var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
30694
30695      var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
30696
30697      _this.manifest = {
30698        allowCache: true,
30699        discontinuityStarts: [],
30700        segments: []
30701      }; // keep track of the last seen segment's byte range end, as segments are not required
30702      // to provide the offset, in which case it defaults to the next byte after the
30703      // previous segment
30704
30705      var lastByterangeEnd = 0; // update the manifest with the m3u8 entry from the parse stream
30706
30707      _this.parseStream.on('data', function (entry) {
30708        var mediaGroup;
30709        var rendition;
30710        ({
30711          tag: function tag() {
30712            // switch based on the tag type
30713            (({
30714              'allow-cache': function allowCache() {
30715                this.manifest.allowCache = entry.allowed;
30716
30717                if (!('allowed' in entry)) {
30718                  this.trigger('info', {
30719                    message: 'defaulting allowCache to YES'
30720                  });
30721                  this.manifest.allowCache = true;
30722                }
30723              },
30724              byterange: function byterange() {
30725                var byterange = {};
30726
30727                if ('length' in entry) {
30728                  currentUri.byterange = byterange;
30729                  byterange.length = entry.length;
30730
30731                  if (!('offset' in entry)) {
30732                    /*
30733                     * From the latest spec (as of this writing):
30734                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
30735                     *
30736                     * Same text since EXT-X-BYTERANGE's introduction in draft 7:
30737                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
30738                     *
30739                     * "If o [offset] is not present, the sub-range begins at the next byte
30740                     * following the sub-range of the previous media segment."
30741                     */
30742                    entry.offset = lastByterangeEnd;
30743                  }
30744                }
30745
30746                if ('offset' in entry) {
30747                  currentUri.byterange = byterange;
30748                  byterange.offset = entry.offset;
30749                }
30750
30751                lastByterangeEnd = byterange.offset + byterange.length;
30752              },
30753              endlist: function endlist() {
30754                this.manifest.endList = true;
30755              },
30756              inf: function inf() {
30757                if (!('mediaSequence' in this.manifest)) {
30758                  this.manifest.mediaSequence = 0;
30759                  this.trigger('info', {
30760                    message: 'defaulting media sequence to zero'
30761                  });
30762                }
30763
30764                if (!('discontinuitySequence' in this.manifest)) {
30765                  this.manifest.discontinuitySequence = 0;
30766                  this.trigger('info', {
30767                    message: 'defaulting discontinuity sequence to zero'
30768                  });
30769                }
30770
30771                if (entry.duration > 0) {
30772                  currentUri.duration = entry.duration;
30773                }
30774
30775                if (entry.duration === 0) {
30776                  currentUri.duration = 0.01;
30777                  this.trigger('info', {
30778                    message: 'updating zero segment duration to a small value'
30779                  });
30780                }
30781
30782                this.manifest.segments = uris;
30783              },
30784              key: function key() {
30785                if (!entry.attributes) {
30786                  this.trigger('warn', {
30787                    message: 'ignoring key declaration without attribute list'
30788                  });
30789                  return;
30790                } // clear the active encryption key
30791
30792
30793                if (entry.attributes.METHOD === 'NONE') {
30794                  _key = null;
30795                  return;
30796                }
30797
30798                if (!entry.attributes.URI) {
30799                  this.trigger('warn', {
30800                    message: 'ignoring key declaration without URI'
30801                  });
30802                  return;
30803                } // check if the content is encrypted for Widevine
30804                // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
30805
30806
30807                if (entry.attributes.KEYFORMAT === widevineUuid) {
30808                  var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
30809
30810                  if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
30811                    this.trigger('warn', {
30812                      message: 'invalid key method provided for Widevine'
30813                    });
30814                    return;
30815                  }
30816
30817                  if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
30818                    this.trigger('warn', {
30819                      message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
30820                    });
30821                  }
30822
30823                  if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
30824                    this.trigger('warn', {
30825                      message: 'invalid key URI provided for Widevine'
30826                    });
30827                    return;
30828                  }
30829
30830                  if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
30831                    this.trigger('warn', {
30832                      message: 'invalid key ID provided for Widevine'
30833                    });
30834                    return;
30835                  } // if Widevine key attributes are valid, store them as `contentProtection`
30836                  // on the manifest to emulate Widevine tag structure in a DASH mpd
30837
30838
30839                  this.manifest.contentProtection = {
30840                    'com.widevine.alpha': {
30841                      attributes: {
30842                        schemeIdUri: entry.attributes.KEYFORMAT,
30843                        // remove '0x' from the key id string
30844                        keyId: entry.attributes.KEYID.substring(2)
30845                      },
30846                      // decode the base64-encoded PSSH box
30847                      pssh: decodeB64ToUint8Array_1(entry.attributes.URI.split(',')[1])
30848                    }
30849                  };
30850                  return;
30851                }
30852
30853                if (!entry.attributes.METHOD) {
30854                  this.trigger('warn', {
30855                    message: 'defaulting key method to AES-128'
30856                  });
30857                } // setup an encryption key for upcoming segments
30858
30859
30860                _key = {
30861                  method: entry.attributes.METHOD || 'AES-128',
30862                  uri: entry.attributes.URI
30863                };
30864
30865                if (typeof entry.attributes.IV !== 'undefined') {
30866                  _key.iv = entry.attributes.IV;
30867                }
30868              },
30869              'media-sequence': function mediaSequence() {
30870                if (!isFinite(entry.number)) {
30871                  this.trigger('warn', {
30872                    message: 'ignoring invalid media sequence: ' + entry.number
30873                  });
30874                  return;
30875                }
30876
30877                this.manifest.mediaSequence = entry.number;
30878              },
30879              'discontinuity-sequence': function discontinuitySequence() {
30880                if (!isFinite(entry.number)) {
30881                  this.trigger('warn', {
30882                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
30883                  });
30884                  return;
30885                }
30886
30887                this.manifest.discontinuitySequence = entry.number;
30888                currentTimeline = entry.number;
30889              },
30890              'playlist-type': function playlistType() {
30891                if (!/VOD|EVENT/.test(entry.playlistType)) {
30892                  this.trigger('warn', {
30893                    message: 'ignoring unknown playlist type: ' + entry.playlist
30894                  });
30895                  return;
30896                }
30897
30898                this.manifest.playlistType = entry.playlistType;
30899              },
30900              map: function map() {
30901                currentMap = {};
30902
30903                if (entry.uri) {
30904                  currentMap.uri = entry.uri;
30905                }
30906
30907                if (entry.byterange) {
30908                  currentMap.byterange = entry.byterange;
30909                }
30910              },
30911              'stream-inf': function streamInf() {
30912                this.manifest.playlists = uris;
30913                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30914
30915                if (!entry.attributes) {
30916                  this.trigger('warn', {
30917                    message: 'ignoring empty stream-inf attributes'
30918                  });
30919                  return;
30920                }
30921
30922                if (!currentUri.attributes) {
30923                  currentUri.attributes = {};
30924                }
30925
30926                _extends_1(currentUri.attributes, entry.attributes);
30927              },
30928              media: function media() {
30929                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30930
30931                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
30932                  this.trigger('warn', {
30933                    message: 'ignoring incomplete or missing media group'
30934                  });
30935                  return;
30936                } // find the media group, creating defaults as necessary
30937
30938
30939                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
30940                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
30941                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
30942
30943                rendition = {
30944                  "default": /yes/i.test(entry.attributes.DEFAULT)
30945                };
30946
30947                if (rendition["default"]) {
30948                  rendition.autoselect = true;
30949                } else {
30950                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
30951                }
30952
30953                if (entry.attributes.LANGUAGE) {
30954                  rendition.language = entry.attributes.LANGUAGE;
30955                }
30956
30957                if (entry.attributes.URI) {
30958                  rendition.uri = entry.attributes.URI;
30959                }
30960
30961                if (entry.attributes['INSTREAM-ID']) {
30962                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
30963                }
30964
30965                if (entry.attributes.CHARACTERISTICS) {
30966                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
30967                }
30968
30969                if (entry.attributes.FORCED) {
30970                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
30971                } // insert the new rendition
30972
30973
30974                mediaGroup[entry.attributes.NAME] = rendition;
30975              },
30976              discontinuity: function discontinuity() {
30977                currentTimeline += 1;
30978                currentUri.discontinuity = true;
30979                this.manifest.discontinuityStarts.push(uris.length);
30980              },
30981              'program-date-time': function programDateTime() {
30982                if (typeof this.manifest.dateTimeString === 'undefined') {
30983                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
30984                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
30985                  // to the manifest object
30986                  // TODO: Consider removing this in future major version
30987                  this.manifest.dateTimeString = entry.dateTimeString;
30988                  this.manifest.dateTimeObject = entry.dateTimeObject;
30989                }
30990
30991                currentUri.dateTimeString = entry.dateTimeString;
30992                currentUri.dateTimeObject = entry.dateTimeObject;
30993              },
30994              targetduration: function targetduration() {
30995                if (!isFinite(entry.duration) || entry.duration < 0) {
30996                  this.trigger('warn', {
30997                    message: 'ignoring invalid target duration: ' + entry.duration
30998                  });
30999                  return;
31000                }
31001
31002                this.manifest.targetDuration = entry.duration;
31003              },
31004              totalduration: function totalduration() {
31005                if (!isFinite(entry.duration) || entry.duration < 0) {
31006                  this.trigger('warn', {
31007                    message: 'ignoring invalid total duration: ' + entry.duration
31008                  });
31009                  return;
31010                }
31011
31012                this.manifest.totalDuration = entry.duration;
31013              },
31014              start: function start() {
31015                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
31016                  this.trigger('warn', {
31017                    message: 'ignoring start declaration without appropriate attribute list'
31018                  });
31019                  return;
31020                }
31021
31022                this.manifest.start = {
31023                  timeOffset: entry.attributes['TIME-OFFSET'],
31024                  precise: entry.attributes.PRECISE
31025                };
31026              },
31027              'cue-out': function cueOut() {
31028                currentUri.cueOut = entry.data;
31029              },
31030              'cue-out-cont': function cueOutCont() {
31031                currentUri.cueOutCont = entry.data;
31032              },
31033              'cue-in': function cueIn() {
31034                currentUri.cueIn = entry.data;
31035              }
31036            })[entry.tagType] || noop).call(self);
31037          },
31038          uri: function uri() {
31039            currentUri.uri = entry.uri;
31040            uris.push(currentUri); // if no explicit duration was declared, use the target duration
31041
31042            if (this.manifest.targetDuration && !('duration' in currentUri)) {
31043              this.trigger('warn', {
31044                message: 'defaulting segment duration to the target duration'
31045              });
31046              currentUri.duration = this.manifest.targetDuration;
31047            } // annotate with encryption information, if necessary
31048
31049
31050            if (_key) {
31051              currentUri.key = _key;
31052            }
31053
31054            currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
31055
31056            if (currentMap) {
31057              currentUri.map = currentMap;
31058            } // prepare for the next URI
31059
31060
31061            currentUri = {};
31062          },
31063          comment: function comment() {// comments are not important for playback
31064          },
31065          custom: function custom() {
31066            // if this is segment-level data attach the output to the segment
31067            if (entry.segment) {
31068              currentUri.custom = currentUri.custom || {};
31069              currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
31070            } else {
31071              this.manifest.custom = this.manifest.custom || {};
31072              this.manifest.custom[entry.customType] = entry.data;
31073            }
31074          }
31075        })[entry.type].call(self);
31076      });
31077
31078      return _this;
31079    }
31080    /**
31081     * Parse the input string and update the manifest object.
31082     *
31083     * @param {string} chunk a potentially incomplete portion of the manifest
31084     */
31085
31086
31087    var _proto = Parser.prototype;
31088
31089    _proto.push = function push(chunk) {
31090      this.lineStream.push(chunk);
31091    }
31092    /**
31093     * Flush any remaining input. This can be handy if the last line of an M3U8
31094     * manifest did not contain a trailing newline but the file has been
31095     * completely received.
31096     */
31097    ;
31098
31099    _proto.end = function end() {
31100      // flush any buffered input
31101      this.lineStream.push('\n');
31102    }
31103    /**
31104     * Add an additional parser for non-standard tags
31105     *
31106     * @param {Object}   options              a map of options for the added parser
31107     * @param {RegExp}   options.expression   a regular expression to match the custom header
31108     * @param {string}   options.type         the type to register to the output
31109     * @param {Function} [options.dataParser] function to parse the line into an object
31110     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
31111     */
31112    ;
31113
31114    _proto.addParser = function addParser(options) {
31115      this.parseStream.addParser(options);
31116    }
31117    /**
31118     * Add a custom header mapper
31119     *
31120     * @param {Object}   options
31121     * @param {RegExp}   options.expression   a regular expression to match the custom header
31122     * @param {Function} options.map          function to translate tag into a different tag
31123     */
31124    ;
31125
31126    _proto.addTagMapper = function addTagMapper(options) {
31127      this.parseStream.addTagMapper(options);
31128    };
31129
31130    return Parser;
31131  }(stream);
31132
31133  var mediaTypes = createCommonjsModule(function (module, exports) {
31134
31135    Object.defineProperty(exports, '__esModule', {
31136      value: true
31137    });
31138    var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
31139    var DASH_REGEX = /^application\/dash\+xml/i;
31140    /**
31141     * Returns a string that describes the type of source based on a video source object's
31142     * media type.
31143     *
31144     * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
31145     *
31146     * @param {string} type
31147     *        Video source object media type
31148     * @return {('hls'|'dash'|'vhs-json'|null)}
31149     *         VHS source type string
31150     */
31151
31152    var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
31153      if (MPEGURL_REGEX.test(type)) {
31154        return 'hls';
31155      }
31156
31157      if (DASH_REGEX.test(type)) {
31158        return 'dash';
31159      } // Denotes the special case of a manifest object passed to http-streaming instead of a
31160      // source URL.
31161      //
31162      // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
31163      //
31164      // In this case, vnd stands for vendor, video.js for the organization, VHS for this
31165      // project, and the +json suffix identifies the structure of the media type.
31166
31167
31168      if (type === 'application/vnd.videojs.vhs+json') {
31169        return 'vhs-json';
31170      }
31171
31172      return null;
31173    };
31174
31175    exports.simpleTypeFromSourceType = simpleTypeFromSourceType;
31176  });
31177  unwrapExports(mediaTypes);
31178  var mediaTypes_1 = mediaTypes.simpleTypeFromSourceType;
31179
31180  //[4]   	NameStartChar	   ::=   	":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
31181  //[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
31182  //[5]   	Name	   ::=   	NameStartChar (NameChar)*
31183  var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/; //\u10000-\uEFFFF
31184
31185  var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
31186  var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
31187  //var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
31188  //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
31189  //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
31190
31191  var S_TAG = 0; //tag name offerring
31192
31193  var S_ATTR = 1; //attr name offerring 
31194
31195  var S_ATTR_SPACE = 2; //attr name end and space offer
31196
31197  var S_EQ = 3; //=space?
31198
31199  var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
31200
31201  var S_ATTR_END = 5; //attr value end and no space(quot end)
31202
31203  var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
31204
31205  var S_TAG_CLOSE = 7; //closed el<el />
31206
31207  function XMLReader() {}
31208
31209  XMLReader.prototype = {
31210    parse: function parse(source, defaultNSMap, entityMap) {
31211      var domBuilder = this.domBuilder;
31212      domBuilder.startDocument();
31213
31214      _copy(defaultNSMap, defaultNSMap = {});
31215
31216      _parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
31217
31218      domBuilder.endDocument();
31219    }
31220  };
31221
31222  function _parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
31223    function fixedFromCharCode(code) {
31224      // String.prototype.fromCharCode does not supports
31225      // > 2 bytes unicode chars directly
31226      if (code > 0xffff) {
31227        code -= 0x10000;
31228        var surrogate1 = 0xd800 + (code >> 10),
31229            surrogate2 = 0xdc00 + (code & 0x3ff);
31230        return String.fromCharCode(surrogate1, surrogate2);
31231      } else {
31232        return String.fromCharCode(code);
31233      }
31234    }
31235
31236    function entityReplacer(a) {
31237      var k = a.slice(1, -1);
31238
31239      if (k in entityMap) {
31240        return entityMap[k];
31241      } else if (k.charAt(0) === '#') {
31242        return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x')));
31243      } else {
vendor: 16,384 bytes, lines 31244-31799
31244        errorHandler.error('entity not found:' + a);
31245        return a;
31246      }
31247    }
31248
31249    function appendText(end) {
31250      //has some bugs
31251      if (end > start) {
31252        var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer);
31253        locator && position(start);
31254        domBuilder.characters(xt, 0, end - start);
31255        start = end;
31256      }
31257    }
31258
31259    function position(p, m) {
31260      while (p >= lineEnd && (m = linePattern.exec(source))) {
31261        lineStart = m.index;
31262        lineEnd = lineStart + m[0].length;
31263        locator.lineNumber++; //console.log('line++:',locator,startPos,endPos)
31264      }
31265
31266      locator.columnNumber = p - lineStart + 1;
31267    }
31268
31269    var lineStart = 0;
31270    var lineEnd = 0;
31271    var linePattern = /.*(?:\r\n?|\n)|.*$/g;
31272    var locator = domBuilder.locator;
31273    var parseStack = [{
31274      currentNSMap: defaultNSMapCopy
31275    }];
31276    var closeMap = {};
31277    var start = 0;
31278
31279    while (true) {
31280      try {
31281        var tagStart = source.indexOf('<', start);
31282
31283        if (tagStart < 0) {
31284          if (!source.substr(start).match(/^\s*$/)) {
31285            var doc = domBuilder.doc;
31286            var text = doc.createTextNode(source.substr(start));
31287            doc.appendChild(text);
31288            domBuilder.currentElement = text;
31289          }
31290
31291          return;
31292        }
31293
31294        if (tagStart > start) {
31295          appendText(tagStart);
31296        }
31297
31298        switch (source.charAt(tagStart + 1)) {
31299          case '/':
31300            var end = source.indexOf('>', tagStart + 3);
31301            var tagName = source.substring(tagStart + 2, end);
31302            var config = parseStack.pop();
31303
31304            if (end < 0) {
31305              tagName = source.substring(tagStart + 2).replace(/[\s<].*/, ''); //console.error('#@@@@@@'+tagName)
31306
31307              errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName);
31308              end = tagStart + 1 + tagName.length;
31309            } else if (tagName.match(/\s</)) {
31310              tagName = tagName.replace(/[\s<].*/, '');
31311              errorHandler.error("end tag name: " + tagName + ' maybe not complete');
31312              end = tagStart + 1 + tagName.length;
31313            } //console.error(parseStack.length,parseStack)
31314            //console.error(config);
31315
31316
31317            var localNSMap = config.localNSMap;
31318            var endMatch = config.tagName == tagName;
31319            var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase();
31320
31321            if (endIgnoreCaseMach) {
31322              domBuilder.endElement(config.uri, config.localName, tagName);
31323
31324              if (localNSMap) {
31325                for (var prefix in localNSMap) {
31326                  domBuilder.endPrefixMapping(prefix);
31327                }
31328              }
31329
31330              if (!endMatch) {
31331                errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName);
31332              }
31333            } else {
31334              parseStack.push(config);
31335            }
31336
31337            end++;
31338            break;
31339          // end elment
31340
31341          case '?':
31342            // <?...?>
31343            locator && position(tagStart);
31344            end = parseInstruction(source, tagStart, domBuilder);
31345            break;
31346
31347          case '!':
31348            // <!doctype,<![CDATA,<!--
31349            locator && position(tagStart);
31350            end = parseDCC(source, tagStart, domBuilder, errorHandler);
31351            break;
31352
31353          default:
31354            locator && position(tagStart);
31355            var el = new ElementAttributes();
31356            var currentNSMap = parseStack[parseStack.length - 1].currentNSMap; //elStartEnd
31357
31358            var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler);
31359            var len = el.length;
31360
31361            if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) {
31362              el.closed = true;
31363
31364              if (!entityMap.nbsp) {
31365                errorHandler.warning('unclosed xml attribute');
31366              }
31367            }
31368
31369            if (locator && len) {
31370              var locator2 = copyLocator(locator, {}); //try{//attribute position fixed
31371
31372              for (var i = 0; i < len; i++) {
31373                var a = el[i];
31374                position(a.offset);
31375                a.locator = copyLocator(locator, {});
31376              } //}catch(e){console.error('@@@@@'+e)}
31377
31378
31379              domBuilder.locator = locator2;
31380
31381              if (appendElement(el, domBuilder, currentNSMap)) {
31382                parseStack.push(el);
31383              }
31384
31385              domBuilder.locator = locator;
31386            } else {
31387              if (appendElement(el, domBuilder, currentNSMap)) {
31388                parseStack.push(el);
31389              }
31390            }
31391
31392            if (el.uri === 'http://www.w3.org/1999/xhtml' && !el.closed) {
31393              end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
31394            } else {
31395              end++;
31396            }
31397
31398        }
31399      } catch (e) {
31400        errorHandler.error('element parse error: ' + e); //errorHandler.error('element parse error: '+e);
31401
31402        end = -1; //throw e;
31403      }
31404
31405      if (end > start) {
31406        start = end;
31407      } else {
31408        //TODO: 这里有可能sax回退,有位置错误风险
31409        appendText(Math.max(tagStart, start) + 1);
31410      }
31411    }
31412  }
31413
31414  function copyLocator(f, t) {
31415    t.lineNumber = f.lineNumber;
31416    t.columnNumber = f.columnNumber;
31417    return t;
31418  }
31419  /**
31420   * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
31421   * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
31422   */
31423
31424
31425  function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) {
31426    var attrName;
31427    var value;
31428    var p = ++start;
31429    var s = S_TAG; //status
31430
31431    while (true) {
31432      var c = source.charAt(p);
31433
31434      switch (c) {
31435        case '=':
31436          if (s === S_ATTR) {
31437            //attrName
31438            attrName = source.slice(start, p);
31439            s = S_EQ;
31440          } else if (s === S_ATTR_SPACE) {
31441            s = S_EQ;
31442          } else {
31443            //fatalError: equal must after attrName or space after attrName
31444            throw new Error('attribute equal must after attrName');
31445          }
31446
31447          break;
31448
31449        case '\'':
31450        case '"':
31451          if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
31452          ) {
31453              //equal
31454              if (s === S_ATTR) {
31455                errorHandler.warning('attribute value must after "="');
31456                attrName = source.slice(start, p);
31457              }
31458
31459              start = p + 1;
31460              p = source.indexOf(c, start);
31461
31462              if (p > 0) {
31463                value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer);
31464                el.add(attrName, value, start - 1);
31465                s = S_ATTR_END;
31466              } else {
31467                //fatalError: no end quot match
31468                throw new Error('attribute value no end \'' + c + '\' match');
31469              }
31470            } else if (s == S_ATTR_NOQUOT_VALUE) {
31471            value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); //console.log(attrName,value,start,p)
31472
31473            el.add(attrName, value, start); //console.dir(el)
31474
31475            errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
31476            start = p + 1;
31477            s = S_ATTR_END;
31478          } else {
31479            //fatalError: no equal before
31480            throw new Error('attribute value must after "="');
31481          }
31482
31483          break;
31484
31485        case '/':
31486          switch (s) {
31487            case S_TAG:
31488              el.setTagName(source.slice(start, p));
31489
31490            case S_ATTR_END:
31491            case S_TAG_SPACE:
31492            case S_TAG_CLOSE:
31493              s = S_TAG_CLOSE;
31494              el.closed = true;
31495
31496            case S_ATTR_NOQUOT_VALUE:
31497            case S_ATTR:
31498            case S_ATTR_SPACE:
31499              break;
31500            //case S_EQ:
31501
31502            default:
31503              throw new Error("attribute invalid close char('/')");
31504          }
31505
31506          break;
31507
31508        case '':
31509          //end document
31510          //throw new Error('unexpected end of input')
31511          errorHandler.error('unexpected end of input');
31512
31513          if (s == S_TAG) {
31514            el.setTagName(source.slice(start, p));
31515          }
31516
31517          return p;
31518
31519        case '>':
31520          switch (s) {
31521            case S_TAG:
31522              el.setTagName(source.slice(start, p));
31523
31524            case S_ATTR_END:
31525            case S_TAG_SPACE:
31526            case S_TAG_CLOSE:
31527              break;
31528            //normal
31529
31530            case S_ATTR_NOQUOT_VALUE: //Compatible state
31531
31532            case S_ATTR:
31533              value = source.slice(start, p);
31534
31535              if (value.slice(-1) === '/') {
31536                el.closed = true;
31537                value = value.slice(0, -1);
31538              }
31539
31540            case S_ATTR_SPACE:
31541              if (s === S_ATTR_SPACE) {
31542                value = attrName;
31543              }
31544
31545              if (s == S_ATTR_NOQUOT_VALUE) {
31546                errorHandler.warning('attribute "' + value + '" missed quot(")!!');
31547                el.add(attrName, value.replace(/&#?\w+;/g, entityReplacer), start);
31548              } else {
31549                if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)) {
31550                  errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
31551                }
31552
31553                el.add(value, value, start);
31554              }
31555
31556              break;
31557
31558            case S_EQ:
31559              throw new Error('attribute value missed!!');
31560          } //			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
31561
31562
31563          return p;
31564
31565        /*xml space '\x20' | #x9 | #xD | #xA; */
31566
31567        case "\x80":
31568          c = ' ';
31569
31570        default:
31571          if (c <= ' ') {
31572            //space
31573            switch (s) {
31574              case S_TAG:
31575                el.setTagName(source.slice(start, p)); //tagName
31576
31577                s = S_TAG_SPACE;
31578                break;
31579
31580              case S_ATTR:
31581                attrName = source.slice(start, p);
31582                s = S_ATTR_SPACE;
31583                break;
31584
31585              case S_ATTR_NOQUOT_VALUE:
31586                var value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer);
31587                errorHandler.warning('attribute "' + value + '" missed quot(")!!');
31588                el.add(attrName, value, start);
31589
31590              case S_ATTR_END:
31591                s = S_TAG_SPACE;
31592                break;
31593              //case S_TAG_SPACE:
31594              //case S_EQ:
31595              //case S_ATTR_SPACE:
31596              //	void();break;
31597              //case S_TAG_CLOSE:
31598              //ignore warning
31599            }
31600          } else {
31601            //not space
31602            //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
31603            //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
31604            switch (s) {
31605              //case S_TAG:void();break;
31606              //case S_ATTR:void();break;
31607              //case S_ATTR_NOQUOT_VALUE:void();break;
31608              case S_ATTR_SPACE:
31609                var tagName = el.tagName;
31610
31611                if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
31612                  errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
31613                }
31614
31615                el.add(attrName, attrName, start);
31616                start = p;
31617                s = S_ATTR;
31618                break;
31619
31620              case S_ATTR_END:
31621                errorHandler.warning('attribute space is required"' + attrName + '"!!');
31622
31623              case S_TAG_SPACE:
31624                s = S_ATTR;
31625                start = p;
31626                break;
31627
31628              case S_EQ:
31629                s = S_ATTR_NOQUOT_VALUE;
31630                start = p;
31631                break;
31632
31633              case S_TAG_CLOSE:
31634                throw new Error("elements closed character '/' and '>' must be connected to");
31635            }
31636          }
31637
31638      } //end outer switch
31639      //console.log('p++',p)
31640
31641
31642      p++;
31643    }
31644  }
31645  /**
31646   * @return true if has new namespace define
31647   */
31648
31649
31650  function appendElement(el, domBuilder, currentNSMap) {
31651    var tagName = el.tagName;
31652    var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
31653
31654    var i = el.length;
31655
31656    while (i--) {
31657      var a = el[i];
31658      var qName = a.qName;
31659      var value = a.value;
31660      var nsp = qName.indexOf(':');
31661
31662      if (nsp > 0) {
31663        var prefix = a.prefix = qName.slice(0, nsp);
31664        var localName = qName.slice(nsp + 1);
31665        var nsPrefix = prefix === 'xmlns' && localName;
31666      } else {
31667        localName = qName;
31668        prefix = null;
31669        nsPrefix = qName === 'xmlns' && '';
31670      } //can not set prefix,because prefix !== ''
31671
31672
31673      a.localName = localName; //prefix == null for no ns prefix attribute 
31674
31675      if (nsPrefix !== false) {
31676        //hack!!
31677        if (localNSMap == null) {
31678          localNSMap = {}; //console.log(currentNSMap,0)
31679
31680          _copy(currentNSMap, currentNSMap = {}); //console.log(currentNSMap,1)
31681
31682        }
31683
31684        currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
31685        a.uri = 'http://www.w3.org/2000/xmlns/';
31686        domBuilder.startPrefixMapping(nsPrefix, value);
31687      }
31688    }
31689
31690    var i = el.length;
31691
31692    while (i--) {
31693      a = el[i];
31694      var prefix = a.prefix;
31695
31696      if (prefix) {
31697        //no prefix attribute has no namespace
31698        if (prefix === 'xml') {
31699          a.uri = 'http://www.w3.org/XML/1998/namespace';
31700        }
31701
31702        if (prefix !== 'xmlns') {
31703          a.uri = currentNSMap[prefix || '']; //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
31704        }
31705      }
31706    }
31707
31708    var nsp = tagName.indexOf(':');
31709
31710    if (nsp > 0) {
31711      prefix = el.prefix = tagName.slice(0, nsp);
31712      localName = el.localName = tagName.slice(nsp + 1);
31713    } else {
31714      prefix = null; //important!!
31715
31716      localName = el.localName = tagName;
31717    } //no prefix element has default namespace
31718
31719
31720    var ns = el.uri = currentNSMap[prefix || ''];
31721    domBuilder.startElement(ns, localName, tagName, el); //endPrefixMapping and startPrefixMapping have not any help for dom builder
31722    //localNSMap = null
31723
31724    if (el.closed) {
31725      domBuilder.endElement(ns, localName, tagName);
31726
31727      if (localNSMap) {
31728        for (prefix in localNSMap) {
31729          domBuilder.endPrefixMapping(prefix);
31730        }
31731      }
31732    } else {
31733      el.currentNSMap = currentNSMap;
31734      el.localNSMap = localNSMap; //parseStack.push(el);
31735
31736      return true;
31737    }
31738  }
31739
31740  function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
31741    if (/^(?:script|textarea)$/i.test(tagName)) {
31742      var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd);
31743      var text = source.substring(elStartEnd + 1, elEndStart);
31744
31745      if (/[&<]/.test(text)) {
31746        if (/^script$/i.test(tagName)) {
31747          //if(!/\]\]>/.test(text)){
31748          //lexHandler.startCDATA();
31749          domBuilder.characters(text, 0, text.length); //lexHandler.endCDATA();
31750
31751          return elEndStart; //}
31752        } //}else{//text area
31753
31754
31755        text = text.replace(/&#?\w+;/g, entityReplacer);
31756        domBuilder.characters(text, 0, text.length);
31757        return elEndStart; //}
31758      }
31759    }
31760
31761    return elStartEnd + 1;
31762  }
31763
31764  function fixSelfClosed(source, elStartEnd, tagName, closeMap) {
31765    //if(tagName in closeMap){
31766    var pos = closeMap[tagName];
31767
31768    if (pos == null) {
31769      //console.log(tagName)
31770      pos = source.lastIndexOf('</' + tagName + '>');
31771
31772      if (pos < elStartEnd) {
31773        //忘记闭合
31774        pos = source.lastIndexOf('</' + tagName);
31775      }
31776
31777      closeMap[tagName] = pos;
31778    }
31779
31780    return pos < elStartEnd; //} 
31781  }
31782
31783  function _copy(source, target) {
31784    for (var n in source) {
31785      target[n] = source[n];
31786    }
31787  }
31788
31789  function parseDCC(source, start, domBuilder, errorHandler) {
31790    //sure start with '<!'
31791    var next = source.charAt(start + 2);
31792
31793    switch (next) {
31794      case '-':
31795        if (source.charAt(start + 3) === '-') {
31796          var end = source.indexOf('-->', start + 4); //append comment source.substring(4,end)//<!--
31797
31798          if (end > start) {
31799            domBuilder.commen
31799t(source, start + 4, end - start - 4);
31800            return end + 3;
31801          } else {
31802            errorHandler.error("Unclosed comment");
31803            return -1;
31804          }
31805        } else {
31806          //error
31807          return -1;
31808        }
31809
31810      default:
31811        if (source.substr(start + 3, 6) == 'CDATA[') {
31812          var end = source.indexOf(']]>', start + 9);
31813          domBuilder.startCDATA();
31814          domBuilder.characters(source, start + 9, end - start - 9);
31815          domBuilder.endCDATA();
31816          return end + 3;
31817        } //<!DOCTYPE
31818        //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) 
31819
31820
31821        var matchs = split(source, start);
31822        var len = matchs.length;
31823
31824        if (len > 1 && /!doctype/i.test(matchs[0][0])) {
31825          var name = matchs[1][0];
31826          var pubid = len > 3 && /^public$/i.test(matchs[2][0]) && matchs[3][0];
31827          var sysid = len > 4 && matchs[4][0];
31828          var lastMatch = matchs[len - 1];
31829          domBuilder.startDTD(name, pubid && pubid.replace(/^(['"])(.*?)\1$/, '$2'), sysid && sysid.replace(/^(['"])(.*?)\1$/, '$2'));
31830          domBuilder.endDTD();
31831          return lastMatch.index + lastMatch[0].length;
31832        }
31833
31834    }
31835
31836    return -1;
31837  }
31838
31839  function parseInstruction(source, start, domBuilder) {
31840    var end = source.indexOf('?>', start);
31841
31842    if (end) {
31843      var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
31844
31845      if (match) {
31846        var len = match[0].length;
31847        domBuilder.processingInstruction(match[1], match[2]);
31848        return end + 2;
31849      } else {
31850        //error
31851        return -1;
31852      }
31853    }
31854
31855    return -1;
31856  }
31857  /**
31858   * @param source
31859   */
31860
31861
31862  function ElementAttributes(source) {}
31863
31864  ElementAttributes.prototype = {
31865    setTagName: function setTagName(tagName) {
31866      if (!tagNamePattern.test(tagName)) {
31867        throw new Error('invalid tagName:' + tagName);
31868      }
31869
31870      this.tagName = tagName;
31871    },
31872    add: function add(qName, value, offset) {
31873      if (!tagNamePattern.test(qName)) {
31874        throw new Error('invalid attribute:' + qName);
31875      }
31876
31877      this[this.length++] = {
31878        qName: qName,
31879        value: value,
31880        offset: offset
31881      };
31882    },
31883    length: 0,
31884    getLocalName: function getLocalName(i) {
31885      return this[i].localName;
31886    },
31887    getLocator: function getLocator(i) {
31888      return this[i].locator;
31889    },
31890    getQName: function getQName(i) {
31891      return this[i].qName;
31892    },
31893    getURI: function getURI(i) {
31894      return this[i].uri;
31895    },
31896    getValue: function getValue(i) {
31897      return this[i].value;
31898    } //	,getIndex:function(uri, localName)){
31899    //		if(localName){
31900    //			
31901    //		}else{
31902    //			var qName = uri
31903    //		}
31904    //	},
31905    //	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
31906    //	getType:function(uri,localName){}
31907    //	getType:function(i){},
31908
31909  };
31910
31911  function _set_proto_(thiz, parent) {
31912    thiz.__proto__ = parent;
31913    return thiz;
31914  }
31915
31916  if (!(_set_proto_({}, _set_proto_.prototype) instanceof _set_proto_)) {
31917    _set_proto_ = function _set_proto_(thiz, parent) {
31918      function p() {}
31919      p.prototype = parent;
31920      p = new p();
31921
31922      for (parent in thiz) {
31923        p[parent] = thiz[parent];
31924      }
31925
31926      return p;
31927    };
31928  }
31929
31930  function split(source, start) {
31931    var match;
31932    var buf = [];
31933    var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
31934    reg.lastIndex = start;
31935    reg.exec(source); //skip <
31936
31937    while (match = reg.exec(source)) {
31938      buf.push(match);
31939      if (match[1]) return buf;
31940    }
31941  }
31942
31943  var XMLReader_1 = XMLReader;
31944  var sax = {
31945    XMLReader: XMLReader_1
31946  };
31947
31948  /*
31949   * DOM Level 2
31950   * Object DOMException
31951   * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
31952   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
31953   */
31954  function copy(src, dest) {
31955    for (var p in src) {
31956      dest[p] = src[p];
31957    }
31958  }
31959  /**
31960  ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
31961  ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
31962   */
31963
31964
31965  function _extends(Class, Super) {
31966    var pt = Class.prototype;
31967
31968    if (Object.create) {
31969      var ppt = Object.create(Super.prototype);
31970      pt.__proto__ = ppt;
31971    }
31972
31973    if (!(pt instanceof Super)) {
31974      var t = function t() {};
31975      t.prototype = Super.prototype;
31976      t = new t();
31977      copy(pt, t);
31978      Class.prototype = pt = t;
31979    }
31980
31981    if (pt.constructor != Class) {
31982      if (typeof Class != 'function') {
31983        console.error("unknow Class:" + Class);
31984      }
31985
31986      pt.constructor = Class;
31987    }
31988  }
31989
31990  var htmlns = 'http://www.w3.org/1999/xhtml'; // Node Types
31991
31992  var NodeType = {};
vendor: 13,342 bytes, lines 31993-32425
31993  var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1;
31994  var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2;
31995  var TEXT_NODE = NodeType.TEXT_NODE = 3;
31996  var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4;
31997  var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5;
31998  var ENTITY_NODE = NodeType.ENTITY_NODE = 6;
31999  var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
32000  var COMMENT_NODE = NodeType.COMMENT_NODE = 8;
32001  var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9;
32002  var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10;
32003  var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11;
32004  var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode
32005
32006  var ExceptionCode = {};
32007  var ExceptionMessage = {};
32008  var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1);
32009  var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2);
32010  var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3);
32011  var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4);
32012  var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5);
32013  var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6);
32014  var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7);
32015  var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8);
32016  var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9);
32017  var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10); //level2
32018
32019  var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11);
32020  var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12);
32021  var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13);
32022  var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14);
32023  var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15);
32024
32025  function DOMException(code, message) {
32026    if (message instanceof Error) {
32027      var error = message;
32028    } else {
32029      error = this;
32030      Error.call(this, ExceptionMessage[code]);
32031      this.message = ExceptionMessage[code];
32032      if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
32033    }
32034
32035    error.code = code;
32036    if (message) this.message = this.message + ": " + message;
32037    return error;
32038  }
32039  DOMException.prototype = Error.prototype;
32040  copy(ExceptionCode, DOMException);
32041  /**
32042   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
32043   * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live.
32044   * The items in the NodeList are accessible via an integral index, starting from 0.
32045   */
32046
32047  function NodeList() {}
32048  NodeList.prototype = {
32049    /**
32050     * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
32051     * @standard level1
32052     */
32053    length: 0,
32054
32055    /**
32056     * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null.
32057     * @standard level1
32058     * @param index  unsigned long 
32059     *   Index into the collection.
32060     * @return Node
32061     * 	The node at the indexth position in the NodeList, or null if that is not a valid index. 
32062     */
32063    item: function item(index) {
32064      return this[index] || null;
32065    },
32066    toString: function toString(isHTML, nodeFilter) {
32067      for (var buf = [], i = 0; i < this.length; i++) {
32068        serializeToString(this[i], buf, isHTML, nodeFilter);
32069      }
32070
32071      return buf.join('');
32072    }
32073  };
32074
32075  function LiveNodeList(node, refresh) {
32076    this._node = node;
32077    this._refresh = refresh;
32078
32079    _updateLiveList(this);
32080  }
32081
32082  function _updateLiveList(list) {
32083    var inc = list._node._inc || list._node.ownerDocument._inc;
32084
32085    if (list._inc != inc) {
32086      var ls = list._refresh(list._node); //console.log(ls.length)
32087
32088
32089      __set__(list, 'length', ls.length);
32090
32091      copy(ls, list);
32092      list._inc = inc;
32093    }
32094  }
32095
32096  LiveNodeList.prototype.item = function (i) {
32097    _updateLiveList(this);
32098
32099    return this[i];
32100  };
32101
32102  _extends(LiveNodeList, NodeList);
32103  /**
32104   * 
32105   * Objects implementing the NamedNodeMap interface are used to represent collections of nodes that can be accessed by name. Note that NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained in any particular order. Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, and does not imply that the DOM specifies an order to these Nodes.
32106   * NamedNodeMap objects in the DOM are live.
32107   * used for attributes or DocumentType entities 
32108   */
32109
32110
32111  function NamedNodeMap() {}
32112
32113  function _findNodeIndex(list, node) {
32114    var i = list.length;
32115
32116    while (i--) {
32117      if (list[i] === node) {
32118        return i;
32119      }
32120    }
32121  }
32122
32123  function _addNamedNode(el, list, newAttr, oldAttr) {
32124    if (oldAttr) {
32125      list[_findNodeIndex(list, oldAttr)] = newAttr;
32126    } else {
32127      list[list.length++] = newAttr;
32128    }
32129
32130    if (el) {
32131      newAttr.ownerElement = el;
32132      var doc = el.ownerDocument;
32133
32134      if (doc) {
32135        oldAttr && _onRemoveAttribute(doc, el, oldAttr);
32136
32137        _onAddAttribute(doc, el, newAttr);
32138      }
32139    }
32140  }
32141
32142  function _removeNamedNode(el, list, attr) {
32143    //console.log('remove attr:'+attr)
32144    var i = _findNodeIndex(list, attr);
32145
32146    if (i >= 0) {
32147      var lastIndex = list.length - 1;
32148
32149      while (i < lastIndex) {
32150        list[i] = list[++i];
32151      }
32152
32153      list.length = lastIndex;
32154
32155      if (el) {
32156        var doc = el.ownerDocument;
32157
32158        if (doc) {
32159          _onRemoveAttribute(doc, el, attr);
32160
32161          attr.ownerElement = null;
32162        }
32163      }
32164    } else {
32165      throw DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
32166    }
32167  }
32168
32169  NamedNodeMap.prototype = {
32170    length: 0,
32171    item: NodeList.prototype.item,
32172    getNamedItem: function getNamedItem(key) {
32173      //		if(key.indexOf(':')>0 || key == 'xmlns'){
32174      //			return null;
32175      //		}
32176      //console.log()
32177      var i = this.length;
32178
32179      while (i--) {
32180        var attr = this[i]; //console.log(attr.nodeName,key)
32181
32182        if (attr.nodeName == key) {
32183          return attr;
32184        }
32185      }
32186    },
32187    setNamedItem: function setNamedItem(attr) {
32188      var el = attr.ownerElement;
32189
32190      if (el && el != this._ownerElement) {
32191        throw new DOMException(INUSE_ATTRIBUTE_ERR);
32192      }
32193
32194      var oldAttr = this.getNamedItem(attr.nodeName);
32195
32196      _addNamedNode(this._ownerElement, this, attr, oldAttr);
32197
32198      return oldAttr;
32199    },
32200
32201    /* returns Node */
32202    setNamedItemNS: function setNamedItemNS(attr) {
32203      // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
32204      var el = attr.ownerElement,
32205          oldAttr;
32206
32207      if (el && el != this._ownerElement) {
32208        throw new DOMException(INUSE_ATTRIBUTE_ERR);
32209      }
32210
32211      oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
32212
32213      _addNamedNode(this._ownerElement, this, attr, oldAttr);
32214
32215      return oldAttr;
32216    },
32217
32218    /* returns Node */
32219    removeNamedItem: function removeNamedItem(key) {
32220      var attr = this.getNamedItem(key);
32221
32222      _removeNamedNode(this._ownerElement, this, attr);
32223
32224      return attr;
32225    },
32226    // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
32227    //for level2
32228    removeNamedItemNS: function removeNamedItemNS(namespaceURI, localName) {
32229      var attr = this.getNamedItemNS(namespaceURI, localName);
32230
32231      _removeNamedNode(this._ownerElement, this, attr);
32232
32233      return attr;
32234    },
32235    getNamedItemNS: function getNamedItemNS(namespaceURI, localName) {
32236      var i = this.length;
32237
32238      while (i--) {
32239        var node = this[i];
32240
32241        if (node.localName == localName && node.namespaceURI == namespaceURI) {
32242          return node;
32243        }
32244      }
32245
32246      return null;
32247    }
32248  };
32249  /**
32250   * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490
32251   */
32252
32253  function DOMImplementation(
32254  /* Object */
32255  features) {
32256    this._features = {};
32257
32258    if (features) {
32259      for (var feature in features) {
32260        this._features = features[feature];
32261      }
32262    }
32263  }
32264  DOMImplementation.prototype = {
32265    hasFeature: function hasFeature(
32266    /* string */
32267    feature,
32268    /* string */
32269    version) {
32270      var versions = this._features[feature.toLowerCase()];
32271
32272      if (versions && (!version || version in versions)) {
32273        return true;
32274      } else {
32275        return false;
32276      }
32277    },
32278    // Introduced in DOM Level 2:
32279    createDocument: function createDocument(namespaceURI, qualifiedName, doctype) {
32280      // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR
32281      var doc = new Document();
32282      doc.implementation = this;
32283      doc.childNodes = new NodeList();
32284      doc.doctype = doctype;
32285
32286      if (doctype) {
32287        doc.appendChild(doctype);
32288      }
32289
32290      if (qualifiedName) {
32291        var root = doc.createElementNS(namespaceURI, qualifiedName);
32292        doc.appendChild(root);
32293      }
32294
32295      return doc;
32296    },
32297    // Introduced in DOM Level 2:
32298    createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) {
32299      // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR
32300      var node = new DocumentType();
32301      node.name = qualifiedName;
32302      node.nodeName = qualifiedName;
32303      node.publicId = publicId;
32304      node.systemId = systemId; // Introduced in DOM Level 2:
32305      //readonly attribute DOMString        internalSubset;
32306      //TODO:..
32307      //  readonly attribute NamedNodeMap     entities;
32308      //  readonly attribute NamedNodeMap     notations;
32309
32310      return node;
32311    }
32312  };
32313  /**
32314   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
32315   */
32316
32317  function Node() {}
32318  Node.prototype = {
32319    firstChild: null,
32320    lastChild: null,
32321    previousSibling: null,
32322    nextSibling: null,
32323    attributes: null,
32324    parentNode: null,
32325    childNodes: null,
32326    ownerDocument: null,
32327    nodeValue: null,
32328    namespaceURI: null,
32329    prefix: null,
32330    localName: null,
32331    // Modified in DOM Level 2:
32332    insertBefore: function insertBefore(newChild, refChild) {
32333      //raises 
32334      return _insertBefore(this, newChild, refChild);
32335    },
32336    replaceChild: function replaceChild(newChild, oldChild) {
32337      //raises 
32338      this.insertBefore(newChild, oldChild);
32339
32340      if (oldChild) {
32341        this.removeChild(oldChild);
32342      }
32343    },
32344    removeChild: function removeChild(oldChild) {
32345      return _removeChild(this, oldChild);
32346    },
32347    appendChild: function appendChild(newChild) {
32348      return this.insertBefore(newChild, null);
32349    },
32350    hasChildNodes: function hasChildNodes() {
32351      return this.firstChild != null;
32352    },
32353    cloneNode: function cloneNode(deep) {
32354      return _cloneNode(this.ownerDocument || this, this, deep);
32355    },
32356    // Modified in DOM Level 2:
32357    normalize: function normalize() {
32358      var child = this.firstChild;
32359
32360      while (child) {
32361        var next = child.nextSibling;
32362
32363        if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) {
32364          this.removeChild(next);
32365          child.appendData(next.data);
32366        } else {
32367          child.normalize();
32368          child = next;
32369        }
32370      }
32371    },
32372    // Introduced in DOM Level 2:
32373    isSupported: function isSupported(feature, version) {
32374      return this.ownerDocument.implementation.hasFeature(feature, version);
32375    },
32376    // Introduced in DOM Level 2:
32377    hasAttributes: function hasAttributes() {
32378      return this.attributes.length > 0;
32379    },
32380    lookupPrefix: function lookupPrefix(namespaceURI) {
32381      var el = this;
32382
32383      while (el) {
32384        var map = el._nsMap; //console.dir(map)
32385
32386        if (map) {
32387          for (var n in map) {
32388            if (map[n] == namespaceURI) {
32389              return n;
32390            }
32391          }
32392        }
32393
32394        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
32395      }
32396
32397      return null;
32398    },
32399    // Introduced in DOM Level 3:
32400    lookupNamespaceURI: function lookupNamespaceURI(prefix) {
32401      var el = this;
32402
32403      while (el) {
32404        var map = el._nsMap; //console.dir(map)
32405
32406        if (map) {
32407          if (prefix in map) {
32408            return map[prefix];
32409          }
32410        }
32411
32412        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
32413      }
32414
32415      return null;
32416    },
32417    // Introduced in DOM Level 3:
32418    isDefaultNamespace: function isDefaultNamespace(namespaceURI) {
32419      var prefix = this.lookupPrefix(namespaceURI);
32420      return prefix == null;
32421    }
32422  };
32423
32424  function _xmlEncoder(c) {
32425    return c == '<' && '&lt;' || c == '>' && '&gt;' || c == '&' && '&amp;' || c == '"' && '&quot;
vendor: 13,054 bytes, lines 32425-32882
32425' || '&#' + c.charCodeAt() + ';';
32426  }
32427
32428  copy(NodeType, Node);
32429  copy(NodeType, Node.prototype);
32430  /**
32431   * @param callback return true for continue,false for break
32432   * @return boolean true: break visit;
32433   */
32434
32435  function _visitNode(node, callback) {
32436    if (callback(node)) {
32437      return true;
32438    }
32439
32440    if (node = node.firstChild) {
32441      do {
32442        if (_visitNode(node, callback)) {
32443          return true;
32444        }
32445      } while (node = node.nextSibling);
32446    }
32447  }
32448
32449  function Document() {}
32450
32451  function _onAddAttribute(doc, el, newAttr) {
32452    doc && doc._inc++;
32453    var ns = newAttr.namespaceURI;
32454
32455    if (ns == 'http://www.w3.org/2000/xmlns/') {
32456      //update namespace
32457      el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value;
32458    }
32459  }
32460
32461  function _onRemoveAttribute(doc, el, newAttr, remove) {
32462    doc && doc._inc++;
32463    var ns = newAttr.namespaceURI;
32464
32465    if (ns == 'http://www.w3.org/2000/xmlns/') {
32466      //update namespace
32467      delete el._nsMap[newAttr.prefix ? newAttr.localName : ''];
32468    }
32469  }
32470
32471  function _onUpdateChild(doc, el, newChild) {
32472    if (doc && doc._inc) {
32473      doc._inc++; //update childNodes
32474
32475      var cs = el.childNodes;
32476
32477      if (newChild) {
32478        cs[cs.length++] = newChild;
32479      } else {
32480        //console.log(1)
32481        var child = el.firstChild;
32482        var i = 0;
32483
32484        while (child) {
32485          cs[i++] = child;
32486          child = child.nextSibling;
32487        }
32488
32489        cs.length = i;
32490      }
32491    }
32492  }
32493  /**
32494   * attributes;
32495   * children;
32496   * 
32497   * writeable properties:
32498   * nodeValue,Attr:value,CharacterData:data
32499   * prefix
32500   */
32501
32502
32503  function _removeChild(parentNode, child) {
32504    var previous = child.previousSibling;
32505    var next = child.nextSibling;
32506
32507    if (previous) {
32508      previous.nextSibling = next;
32509    } else {
32510      parentNode.firstChild = next;
32511    }
32512
32513    if (next) {
32514      next.previousSibling = previous;
32515    } else {
32516      parentNode.lastChild = previous;
32517    }
32518
32519    _onUpdateChild(parentNode.ownerDocument, parentNode);
32520
32521    return child;
32522  }
32523  /**
32524   * preformance key(refChild == null)
32525   */
32526
32527
32528  function _insertBefore(parentNode, newChild, nextChild) {
32529    var cp = newChild.parentNode;
32530
32531    if (cp) {
32532      cp.removeChild(newChild); //remove and update
32533    }
32534
32535    if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
32536      var newFirst = newChild.firstChild;
32537
32538      if (newFirst == null) {
32539        return newChild;
32540      }
32541
32542      var newLast = newChild.lastChild;
32543    } else {
32544      newFirst = newLast = newChild;
32545    }
32546
32547    var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild;
32548    newFirst.previousSibling = pre;
32549    newLast.nextSibling = nextChild;
32550
32551    if (pre) {
32552      pre.nextSibling = newFirst;
32553    } else {
32554      parentNode.firstChild = newFirst;
32555    }
32556
32557    if (nextChild == null) {
32558      parentNode.lastChild = newLast;
32559    } else {
32560      nextChild.previousSibling = newLast;
32561    }
32562
32563    do {
32564      newFirst.parentNode = parentNode;
32565    } while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
32566
32567    _onUpdateChild(parentNode.ownerDocument || parentNode, parentNode); //console.log(parentNode.lastChild.nextSibling == null)
32568
32569
32570    if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
32571      newChild.firstChild = newChild.lastChild = null;
32572    }
32573
32574    return newChild;
32575  }
32576
32577  function _appendSingleChild(parentNode, newChild) {
32578    var cp = newChild.parentNode;
32579
32580    if (cp) {
32581      var pre = parentNode.lastChild;
32582      cp.removeChild(newChild); //remove and update
32583
32584      var pre = parentNode.lastChild;
32585    }
32586
32587    var pre = parentNode.lastChild;
32588    newChild.parentNode = parentNode;
32589    newChild.previousSibling = pre;
32590    newChild.nextSibling = null;
32591
32592    if (pre) {
32593      pre.nextSibling = newChild;
32594    } else {
32595      parentNode.firstChild = newChild;
32596    }
32597
32598    parentNode.lastChild = newChild;
32599
32600    _onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
32601
32602    return newChild; //console.log("__aa",parentNode.lastChild.nextSibling == null)
32603  }
32604
32605  Document.prototype = {
32606    //implementation : null,
32607    nodeName: '#document',
32608    nodeType: DOCUMENT_NODE,
32609    doctype: null,
32610    documentElement: null,
32611    _inc: 1,
32612    insertBefore: function insertBefore(newChild, refChild) {
32613      //raises 
32614      if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
32615        var child = newChild.firstChild;
32616
32617        while (child) {
32618          var next = child.nextSibling;
32619          this.insertBefore(child, refChild);
32620          child = next;
32621        }
32622
32623        return newChild;
32624      }
32625
32626      if (this.documentElement == null && newChild.nodeType == ELEMENT_NODE) {
32627        this.documentElement = newChild;
32628      }
32629
32630      return _insertBefore(this, newChild, refChild), newChild.ownerDocument = this, newChild;
32631    },
32632    removeChild: function removeChild(oldChild) {
32633      if (this.documentElement == oldChild) {
32634        this.documentElement = null;
32635      }
32636
32637      return _removeChild(this, oldChild);
32638    },
32639    // Introduced in DOM Level 2:
32640    importNode: function importNode(importedNode, deep) {
32641      return _importNode(this, importedNode, deep);
32642    },
32643    // Introduced in DOM Level 2:
32644    getElementById: function getElementById(id) {
32645      var rtv = null;
32646
32647      _visitNode(this.documentElement, function (node) {
32648        if (node.nodeType == ELEMENT_NODE) {
32649          if (node.getAttribute('id') == id) {
32650            rtv = node;
32651            return true;
32652          }
32653        }
32654      });
32655
32656      return rtv;
32657    },
32658    //document factory method:
32659    createElement: function createElement(tagName) {
32660      var node = new Element();
32661      node.ownerDocument = this;
32662      node.nodeName = tagName;
32663      node.tagName = tagName;
32664      node.childNodes = new NodeList();
32665      var attrs = node.attributes = new NamedNodeMap();
32666      attrs._ownerElement = node;
32667      return node;
32668    },
32669    createDocumentFragment: function createDocumentFragment() {
32670      var node = new DocumentFragment();
32671      node.ownerDocument = this;
32672      node.childNodes = new NodeList();
32673      return node;
32674    },
32675    createTextNode: function createTextNode(data) {
32676      var node = new Text();
32677      node.ownerDocument = this;
32678      node.appendData(data);
32679      return node;
32680    },
32681    createComment: function createComment(data) {
32682      var node = new Comment();
32683      node.ownerDocument = this;
32684      node.appendData(data);
32685      return node;
32686    },
32687    createCDATASection: function createCDATASection(data) {
32688      var node = new CDATASection();
32689      node.ownerDocument = this;
32690      node.appendData(data);
32691      return node;
32692    },
32693    createProcessingInstruction: function createProcessingInstruction(target, data) {
32694      var node = new ProcessingInstruction();
32695      node.ownerDocument = this;
32696      node.tagName = node.target = target;
32697      node.nodeValue = node.data = data;
32698      return node;
32699    },
32700    createAttribute: function createAttribute(name) {
32701      var node = new Attr();
32702      node.ownerDocument = this;
32703      node.name = name;
32704      node.nodeName = name;
32705      node.localName = name;
32706      node.specified = true;
32707      return node;
32708    },
32709    createEntityReference: function createEntityReference(name) {
32710      var node = new EntityReference();
32711      node.ownerDocument = this;
32712      node.nodeName = name;
32713      return node;
32714    },
32715    // Introduced in DOM Level 2:
32716    createElementNS: function createElementNS(namespaceURI, qualifiedName) {
32717      var node = new Element();
32718      var pl = qualifiedName.split(':');
32719      var attrs = node.attributes = new NamedNodeMap();
32720      node.childNodes = new NodeList();
32721      node.ownerDocument = this;
32722      node.nodeName = qualifiedName;
32723      node.tagName = qualifiedName;
32724      node.namespaceURI = namespaceURI;
32725
32726      if (pl.length == 2) {
32727        node.prefix = pl[0];
32728        node.localName = pl[1];
32729      } else {
32730        //el.prefix = null;
32731        node.localName = qualifiedName;
32732      }
32733
32734      attrs._ownerElement = node;
32735      return node;
32736    },
32737    // Introduced in DOM Level 2:
32738    createAttributeNS: function createAttributeNS(namespaceURI, qualifiedName) {
32739      var node = new Attr();
32740      var pl = qualifiedName.split(':');
32741      node.ownerDocument = this;
32742      node.nodeName = qualifiedName;
32743      node.name = qualifiedName;
32744      node.namespaceURI = namespaceURI;
32745      node.specified = true;
32746
32747      if (pl.length == 2) {
32748        node.prefix = pl[0];
32749        node.localName = pl[1];
32750      } else {
32751        //el.prefix = null;
32752        node.localName = qualifiedName;
32753      }
32754
32755      return node;
32756    }
32757  };
32758
32759  _extends(Document, Node);
32760
32761  function Element() {
32762    this._nsMap = {};
32763  }
32764  Element.prototype = {
32765    nodeType: ELEMENT_NODE,
32766    hasAttribute: function hasAttribute(name) {
32767      return this.getAttributeNode(name) != null;
32768    },
32769    getAttribute: function getAttribute(name) {
32770      var attr = this.getAttributeNode(name);
32771      return attr && attr.value || '';
32772    },
32773    getAttributeNode: function getAttributeNode(name) {
32774      return this.attributes.getNamedItem(name);
32775    },
32776    setAttribute: function setAttribute(name, value) {
32777      var attr = this.ownerDocument.createAttribute(name);
32778      attr.value = attr.nodeValue = "" + value;
32779      this.setAttributeNode(attr);
32780    },
32781    removeAttribute: function removeAttribute(name) {
32782      var attr = this.getAttributeNode(name);
32783      attr && this.removeAttributeNode(attr);
32784    },
32785    //four real opeartion method
32786    appendChild: function appendChild(newChild) {
32787      if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
32788        return this.insertBefore(newChild, null);
32789      } else {
32790        return _appendSingleChild(this, newChild);
32791      }
32792    },
32793    setAttributeNode: function setAttributeNode(newAttr) {
32794      return this.attributes.setNamedItem(newAttr);
32795    },
32796    setAttributeNodeNS: function setAttributeNodeNS(newAttr) {
32797      return this.attributes.setNamedItemNS(newAttr);
32798    },
32799    removeAttributeNode: function removeAttributeNode(oldAttr) {
32800      //console.log(this == oldAttr.ownerElement)
32801      return this.attributes.removeNamedItem(oldAttr.nodeName);
32802    },
32803    //get real attribute name,and remove it by removeAttributeNode
32804    removeAttributeNS: function removeAttributeNS(namespaceURI, localName) {
32805      var old = this.getAttributeNodeNS(namespaceURI, localName);
32806      old && this.removeAttributeNode(old);
32807    },
32808    hasAttributeNS: function hasAttributeNS(namespaceURI, localName) {
32809      return this.getAttributeNodeNS(namespaceURI, localName) != null;
32810    },
32811    getAttributeNS: function getAttributeNS(namespaceURI, localName) {
32812      var attr = this.getAttributeNodeNS(namespaceURI, localName);
32813      return attr && attr.value || '';
32814    },
32815    setAttributeNS: function setAttributeNS(namespaceURI, qualifiedName, value) {
32816      var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
32817      attr.value = attr.nodeValue = "" + value;
32818      this.setAttributeNode(attr);
32819    },
32820    getAttributeNodeNS: function getAttributeNodeNS(namespaceURI, localName) {
32821      return this.attributes.getNamedItemNS(namespaceURI, localName);
32822    },
32823    getElementsByTagName: function getElementsByTagName(tagName) {
32824      return new LiveNodeList(this, function (base) {
32825        var ls = [];
32826
32827        _visitNode(base, function (node) {
32828          if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) {
32829            ls.push(node);
32830          }
32831        });
32832
32833        return ls;
32834      });
32835    },
32836    getElementsByTagNameNS: function getElementsByTagNameNS(namespaceURI, localName) {
32837      return new LiveNodeList(this, function (base) {
32838        var ls = [];
32839
32840        _visitNode(base, function (node) {
32841          if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) {
32842            ls.push(node);
32843          }
32844        });
32845
32846        return ls;
32847      });
32848    }
32849  };
32850  Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
32851  Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
32852
32853  _extends(Element, Node);
32854
32855  function Attr() {}
32856  Attr.prototype.nodeType = ATTRIBUTE_NODE;
32857
32858  _extends(Attr, Node);
32859
32860  function CharacterData() {}
32861  CharacterData.prototype = {
32862    data: '',
32863    substringData: function substringData(offset, count) {
32864      return this.data.substring(offset, offset + count);
32865    },
32866    appendData: function appendData(text) {
32867      text = this.data + text;
32868      this.nodeValue = this.data = text;
32869      this.length = text.length;
32870    },
32871    insertData: function insertData(offset, text) {
32872      this.replaceData(offset, 0, text);
32873    },
32874    appendChild: function appendChild(newChild) {
32875      throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]);
32876    },
32877    deleteData: function deleteData(offset, count) {
32878      this.replaceData(offset, count, "");
32879    },
32880    replaceData: function replaceData(offset, count, text) {
32881      var start = this.data.substring(0, offset);
32882      var end = this.data.substring(offset + count);
vendor: 8,936 bytes, lines 32883-33214
32883      text = start + text + end;
32884      this.nodeValue = this.data = text;
32885      this.length = text.length;
32886    }
32887  };
32888
32889  _extends(CharacterData, Node);
32890
32891  function Text() {}
32892  Text.prototype = {
32893    nodeName: "#text",
32894    nodeType: TEXT_NODE,
32895    splitText: function splitText(offset) {
32896      var text = this.data;
32897      var newText = text.substring(offset);
32898      text = text.substring(0, offset);
32899      this.data = this.nodeValue = text;
32900      this.length = text.length;
32901      var newNode = this.ownerDocument.createTextNode(newText);
32902
32903      if (this.parentNode) {
32904        this.parentNode.insertBefore(newNode, this.nextSibling);
32905      }
32906
32907      return newNode;
32908    }
32909  };
32910
32911  _extends(Text, CharacterData);
32912
32913  function Comment() {}
32914  Comment.prototype = {
32915    nodeName: "#comment",
32916    nodeType: COMMENT_NODE
32917  };
32918
32919  _extends(Comment, CharacterData);
32920
32921  function CDATASection() {}
32922  CDATASection.prototype = {
32923    nodeName: "#cdata-section",
32924    nodeType: CDATA_SECTION_NODE
32925  };
32926
32927  _extends(CDATASection, CharacterData);
32928
32929  function DocumentType() {}
32930  DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
32931
32932  _extends(DocumentType, Node);
32933
32934  function Notation() {}
32935  Notation.prototype.nodeType = NOTATION_NODE;
32936
32937  _extends(Notation, Node);
32938
32939  function Entity() {}
32940  Entity.prototype.nodeType = ENTITY_NODE;
32941
32942  _extends(Entity, Node);
32943
32944  function EntityReference() {}
32945  EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
32946
32947  _extends(EntityReference, Node);
32948
32949  function DocumentFragment() {}
32950  DocumentFragment.prototype.nodeName = "#document-fragment";
32951  DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
32952
32953  _extends(DocumentFragment, Node);
32954
32955  function ProcessingInstruction() {}
32956
32957  ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
32958
32959  _extends(ProcessingInstruction, Node);
32960
32961  function XMLSerializer() {}
32962
32963  XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) {
32964    return nodeSerializeToString.call(node, isHtml, nodeFilter);
32965  };
32966
32967  Node.prototype.toString = nodeSerializeToString;
32968
32969  function nodeSerializeToString(isHtml, nodeFilter) {
32970    var buf = [];
32971    var refNode = this.nodeType == 9 ? this.documentElement : this;
32972    var prefix = refNode.prefix;
32973    var uri = refNode.namespaceURI;
32974
32975    if (uri && prefix == null) {
32976      //console.log(prefix)
32977      var prefix = refNode.lookupPrefix(uri);
32978
32979      if (prefix == null) {
32980        //isHTML = true;
32981        var visibleNamespaces = [{
32982          namespace: uri,
32983          prefix: null
32984        } //{namespace:uri,prefix:''}
32985        ];
32986      }
32987    }
32988
32989    serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join(''))
32990
32991    return buf.join('');
32992  }
32993
32994  function needNamespaceDefine(node, isHTML, visibleNamespaces) {
32995    var prefix = node.prefix || '';
32996    var uri = node.namespaceURI;
32997
32998    if (!prefix && !uri) {
32999      return false;
33000    }
33001
33002    if (prefix === "xml" && uri === "http://www.w3.org/XML/1998/namespace" || uri == 'http://www.w3.org/2000/xmlns/') {
33003      return false;
33004    }
33005
33006    var i = visibleNamespaces.length; //console.log('@@@@',node.tagName,prefix,uri,visibleNamespaces)
33007
33008    while (i--) {
33009      var ns = visibleNamespaces[i]; // get namespace prefix
33010      //console.log(node.nodeType,node.tagName,ns.prefix,prefix)
33011
33012      if (ns.prefix == prefix) {
33013        return ns.namespace != uri;
33014      }
33015    } //console.log(isHTML,uri,prefix=='')
33016    //if(isHTML && prefix ==null && uri == 'http://www.w3.org/1999/xhtml'){
33017    //	return false;
33018    //}
33019    //node.flag = '11111'
33020    //console.error(3,true,node.flag,node.prefix,node.namespaceURI)
33021
33022
33023    return true;
33024  }
33025
33026  function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) {
33027    if (nodeFilter) {
33028      node = nodeFilter(node);
33029
33030      if (node) {
33031        if (typeof node == 'string') {
33032          buf.push(node);
33033          return;
33034        }
33035      } else {
33036        return;
33037      } //buf.sort.apply(attrs, attributeSorter);
33038
33039    }
33040
33041    switch (node.nodeType) {
33042      case ELEMENT_NODE:
33043        if (!visibleNamespaces) visibleNamespaces = [];
33044        var startVisibleNamespaces = visibleNamespaces.length;
33045        var attrs = node.attributes;
33046        var len = attrs.length;
33047        var child = node.firstChild;
33048        var nodeName = node.tagName;
33049        isHTML = htmlns === node.namespaceURI || isHTML;
33050        buf.push('<', nodeName);
33051
33052        for (var i = 0; i < len; i++) {
33053          // add namespaces for attributes
33054          var attr = attrs.item(i);
33055
33056          if (attr.prefix == 'xmlns') {
33057            visibleNamespaces.push({
33058              prefix: attr.localName,
33059              namespace: attr.value
33060            });
33061          } else if (attr.nodeName == 'xmlns') {
33062            visibleNamespaces.push({
33063              prefix: '',
33064              namespace: attr.value
33065            });
33066          }
33067        }
33068
33069        for (var i = 0; i < len; i++) {
33070          var attr = attrs.item(i);
33071
33072          if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) {
33073            var prefix = attr.prefix || '';
33074            var uri = attr.namespaceURI;
33075            var ns = prefix ? ' xmlns:' + prefix : " xmlns";
33076            buf.push(ns, '="', uri, '"');
33077            visibleNamespaces.push({
33078              prefix: prefix,
33079              namespace: uri
33080            });
33081          }
33082
33083          serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces);
33084        } // add namespace for current node		
33085
33086
33087        if (needNamespaceDefine(node, isHTML, visibleNamespaces)) {
33088          var prefix = node.prefix || '';
33089          var uri = node.namespaceURI;
33090          var ns = prefix ? ' xmlns:' + prefix : " xmlns";
33091          buf.push(ns, '="', uri, '"');
33092          visibleNamespaces.push({
33093            prefix: prefix,
33094            namespace: uri
33095          });
33096        }
33097
33098        if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
33099          buf.push('>'); //if is cdata child node
33100
33101          if (isHTML && /^script$/i.test(nodeName)) {
33102            while (child) {
33103              if (child.data) {
33104                buf.push(child.data);
33105              } else {
33106                serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces);
33107              }
33108
33109              child = child.nextSibling;
33110            }
33111          } else {
33112            while (child) {
33113              serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces);
33114              child = child.nextSibling;
33115            }
33116          }
33117
33118          buf.push('</', nodeName, '>');
33119        } else {
33120          buf.push('/>');
33121        } // remove added visible namespaces
33122        //visibleNamespaces.length = startVisibleNamespaces;
33123
33124
33125        return;
33126
33127      case DOCUMENT_NODE:
33128      case DOCUMENT_FRAGMENT_NODE:
33129        var child = node.firstChild;
33130
33131        while (child) {
33132          serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces);
33133          child = child.nextSibling;
33134        }
33135
33136        return;
33137
33138      case ATTRIBUTE_NODE:
33139        return buf.push(' ', node.name, '="', node.value.replace(/[<&"]/g, _xmlEncoder), '"');
33140
33141      case TEXT_NODE:
33142        return buf.push(node.data.replace(/[<&]/g, _xmlEncoder));
33143
33144      case CDATA_SECTION_NODE:
33145        return buf.push('<![CDATA[', node.data, ']]>');
33146
33147      case COMMENT_NODE:
33148        return buf.push("<!--", node.data, "-->");
33149
33150      case DOCUMENT_TYPE_NODE:
33151        var pubid = node.publicId;
33152        var sysid = node.systemId;
33153        buf.push('<!DOCTYPE ', node.name);
33154
33155        if (pubid) {
33156          buf.push(' PUBLIC "', pubid);
33157
33158          if (sysid && sysid != '.') {
33159            buf.push('" "', sysid);
33160          }
33161
33162          buf.push('">');
33163        } else if (sysid && sysid != '.') {
33164          buf.push(' SYSTEM "', sysid, '">');
33165        } else {
33166          var sub = node.internalSubset;
33167
33168          if (sub) {
33169            buf.push(" [", sub, "]");
33170          }
33171
33172          buf.push(">");
33173        }
33174
33175        return;
33176
33177      case PROCESSING_INSTRUCTION_NODE:
33178        return buf.push("<?", node.target, " ", node.data, "?>");
33179
33180      case ENTITY_REFERENCE_NODE:
33181        return buf.push('&', node.nodeName, ';');
33182      //case ENTITY_NODE:
33183      //case NOTATION_NODE:
33184
33185      default:
33186        buf.push('??', node.nodeName);
33187    }
33188  }
33189
33190  function _importNode(doc, node, deep) {
33191    var node2;
33192
33193    switch (node.nodeType) {
33194      case ELEMENT_NODE:
33195        node2 = node.cloneNode(false);
33196        node2.ownerDocument = doc;
33197      //var attrs = node2.attributes;
33198      //var len = attrs.length;
33199      //for(var i=0;i<len;i++){
33200      //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
33201      //}
33202
33203      case DOCUMENT_FRAGMENT_NODE:
33204        break;
33205
33206      case ATTRIBUTE_NODE:
33207        deep = true;
33208        break;
33209      //case ENTITY_REFERENCE_NODE:
33210      //case PROCESSING_INSTRUCTION_NODE:
33211      ////case TEXT_NODE:
33212      //case CDATA_SECTION_NODE:
33213      //case COMMENT_NODE:
33214      //	deep = false;
vendor: 6,277 bytes, lines 33215-33467
33215      //	break;
33216      //case DOCUMENT_NODE:
33217      //case DOCUMENT_TYPE_NODE:
33218      //cannot be imported.
33219      //case ENTITY_NODE:
33220      //case NOTATION_NODE:
33221      //can not hit in level3
33222      //default:throw e;
33223    }
33224
33225    if (!node2) {
33226      node2 = node.cloneNode(false); //false
33227    }
33228
33229    node2.ownerDocument = doc;
33230    node2.parentNode = null;
33231
33232    if (deep) {
33233      var child = node.firstChild;
33234
33235      while (child) {
33236        node2.appendChild(_importNode(doc, child, deep));
33237        child = child.nextSibling;
33238      }
33239    }
33240
33241    return node2;
33242  } //
33243  //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
33244  //					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
33245
33246
33247  function _cloneNode(doc, node, deep) {
33248    var node2 = new node.constructor();
33249
33250    for (var n in node) {
33251      var v = node[n];
33252
33253      if (typeof v != 'object') {
33254        if (v != node2[n]) {
33255          node2[n] = v;
33256        }
33257      }
33258    }
33259
33260    if (node.childNodes) {
33261      node2.childNodes = new NodeList();
33262    }
33263
33264    node2.ownerDocument = doc;
33265
33266    switch (node2.nodeType) {
33267      case ELEMENT_NODE:
33268        var attrs = node.attributes;
33269        var attrs2 = node2.attributes = new NamedNodeMap();
33270        var len = attrs.length;
33271        attrs2._ownerElement = node2;
33272
33273        for (var i = 0; i < len; i++) {
33274          node2.setAttributeNode(_cloneNode(doc, attrs.item(i), true));
33275        }
33276
33277        break;
33278
33279      case ATTRIBUTE_NODE:
33280        deep = true;
33281    }
33282
33283    if (deep) {
33284      var child = node.firstChild;
33285
33286      while (child) {
33287        node2.appendChild(_cloneNode(doc, child, deep));
33288        child = child.nextSibling;
33289      }
33290    }
33291
33292    return node2;
33293  }
33294
33295  function __set__(object, key, value) {
33296    object[key] = value;
33297  } //do dynamic
33298
33299
33300  try {
33301    if (Object.defineProperty) {
33302      var getTextContent = function getTextContent(node) {
33303        switch (node.nodeType) {
33304          case ELEMENT_NODE:
33305          case DOCUMENT_FRAGMENT_NODE:
33306            var buf = [];
33307            node = node.firstChild;
33308
33309            while (node) {
33310              if (node.nodeType !== 7 && node.nodeType !== 8) {
33311                buf.push(getTextContent(node));
33312              }
33313
33314              node = node.nextSibling;
33315            }
33316
33317            return buf.join('');
33318
33319          default:
33320            return node.nodeValue;
33321        }
33322      };
33323
33324      Object.defineProperty(LiveNodeList.prototype, 'length', {
33325        get: function get() {
33326          _updateLiveList(this);
33327
33328          return this.$$length;
33329        }
33330      });
33331      Object.defineProperty(Node.prototype, 'textContent', {
33332        get: function get() {
33333          return getTextContent(this);
33334        },
33335        set: function set(data) {
33336          switch (this.nodeType) {
33337            case ELEMENT_NODE:
33338            case DOCUMENT_FRAGMENT_NODE:
33339              while (this.firstChild) {
33340                this.removeChild(this.firstChild);
33341              }
33342
33343              if (data || String(data)) {
33344                this.appendChild(this.ownerDocument.createTextNode(data));
33345              }
33346
33347              break;
33348
33349            default:
33350              //TODO:
33351              this.data = data;
33352              this.value = data;
33353              this.nodeValue = data;
33354          }
33355        }
33356      });
33357
33358      __set__ = function __set__(object, key, value) {
33359        //console.log(value)
33360        object['$$' + key] = value;
33361      };
33362    }
33363  } catch (e) {} //ie8
33364  //if(typeof require == 'function'){
33365
33366
33367  var DOMImplementation_1 = DOMImplementation;
33368  var XMLSerializer_1 = XMLSerializer; //}
33369
33370  var dom = {
33371    DOMImplementation: DOMImplementation_1,
33372    XMLSerializer: XMLSerializer_1
33373  };
33374
33375  var domParser = createCommonjsModule(function (module, exports) {
33376    function DOMParser(options) {
33377      this.options = options || {
33378        locator: {}
33379      };
33380    }
33381
33382    DOMParser.prototype.parseFromString = function (source, mimeType) {
33383      var options = this.options;
33384      var sax = new XMLReader();
33385      var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler
33386
33387      var errorHandler = options.errorHandler;
33388      var locator = options.locator;
33389      var defaultNSMap = options.xmlns || {};
33390      var entityMap = {
33391        'lt': '<',
33392        'gt': '>',
33393        'amp': '&',
33394        'quot': '"',
33395        'apos': "'"
33396      };
33397
33398      if (locator) {
33399        domBuilder.setDocumentLocator(locator);
33400      }
33401
33402      sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator);
33403      sax.domBuilder = options.domBuilder || domBuilder;
33404
33405      if (/\/x?html?$/.test(mimeType)) {
33406        entityMap.nbsp = '\xa0';
33407        entityMap.copy = '\xa9';
33408        defaultNSMap[''] = 'http://www.w3.org/1999/xhtml';
33409      }
33410
33411      defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace';
33412
33413      if (source) {
33414        sax.parse(source, defaultNSMap, entityMap);
33415      } else {
33416        sax.errorHandler.error("invalid doc source");
33417      }
33418
33419      return domBuilder.doc;
33420    };
33421
33422    function buildErrorHandler(errorImpl, domBuilder, locator) {
33423      if (!errorImpl) {
33424        if (domBuilder instanceof DOMHandler) {
33425          return domBuilder;
33426        }
33427
33428        errorImpl = domBuilder;
33429      }
33430
33431      var errorHandler = {};
33432      var isCallback = errorImpl instanceof Function;
33433      locator = locator || {};
33434
33435      function build(key) {
33436        var fn = errorImpl[key];
33437
33438        if (!fn && isCallback) {
33439          fn = errorImpl.length == 2 ? function (msg) {
33440            errorImpl(key, msg);
33441          } : errorImpl;
33442        }
33443
33444        errorHandler[key] = fn && function (msg) {
33445          fn('[xmldom ' + key + ']\t' + msg + _locator(locator));
33446        } || function () {};
33447      }
33448
33449      build('warning');
33450      build('error');
33451      build('fatalError');
33452      return errorHandler;
33453    } //console.log('#\n\n\n\n\n\n\n####')
33454
33455    /**
33456     * +ContentHandler+ErrorHandler
33457     * +LexicalHandler+EntityResolver2
33458     * -DeclHandler-DTDHandler 
33459     * 
33460     * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
33461     * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
33462     * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
33463     */
33464
33465
33466    function DOMHandler() {
33467      this.cdata = false;
33468    }
33469
33470    function position(locator, node) {
33471      node.lineNumber = locator.lineNumber;
33472      node.columnNumber = locator.columnNumber;
33473    }
33474    /**
33475     * @see org.xml.sax.ContentHandler#startDocument
33476     * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
33477     */
33478
33479
33480    DOMHandler.prototype = {
33481      startDocument: function startDocument() {
33482        this.doc = new DOMImplementation().createDocument(null, null, null);
33483
33484        if (this.locator) {
33485          this.doc.documentURI = this.locator.systemId;
33486        }
33487      },
33488      startElement: function startElement(namespaceURI, localName, qName, attrs) {
33489        var doc = this.doc;
33490        var el = doc.createElementNS(namespaceURI, qName || localName);
33491        var len = attrs.length;
33492        appendElement(this, el);
33493        this.currentElement = el;
33494        this.locator && position(this.locator, el);
33495
33496        for (var i = 0; i < len; i++) {
33497          var namespaceURI = attrs.getURI(i);
33498          var value = attrs.getValue(i);
33499          var qName = attrs.getQName(i);
33500          var attr = doc.createAttributeNS(namespaceURI, qName);
33501          this.locator && position(attrs.getLocator(i), attr);
33502          attr.value = attr.nodeValue = value;
33503          el.setAttributeNode(attr);
33504        }
33505      },
33506      endElement: function endElement(namespaceURI, localName, qName) {
33507        var current = this.currentElement;
33508        var tagName = current.tagName;
33509        this.currentElement = current.parentNode;
33510      },
33511      startPrefixMapping: function startPrefixMapping(prefix, uri) {},
33512      endPrefixMapping: function endPrefixMapping(prefix) {},
33513      processingInstruction: function processingInstruction(target, data) {
33514        var ins = this.doc.createProcessingInstruction(target, data);
33515        this.locator && position(this.locator, ins);
33516        appendElement(this, ins);
33517      },
33518      ignorableWhitespace: function ignorableWhitespace(ch, start, length) {},
33519      characters: function characters(chars, start, length) {
33520        chars = _toString.apply(this, arguments); //console.log(chars)
33521
33522        if (chars) {
33523          if (this.cdata) {
33524            var charNode = this.doc.createCDATASection(chars);
33525          } else {
33526            var charNode = this.doc.createTextNode(chars);
33527          }
33528
33529          if (this.currentElement) {
33530            this.currentElement.appendChild(charNode);
33531          } else if (/^\s*$/.test(chars)) {
33532            this.doc.appendChild(charNode); //process xml
33533          }
33534
33535          this.locator && position(this.locator, charNode);
33536        }
33537      },
33538      skippedEntity: function skippedEntity(name) {},
33539      endDocument: function endDocument() {
33540        this.doc.normalize();
33541      },
33542      setDocumentLocator: function setDocumentLocator(locator) {
33543        if (this.locator = locator) {
33544          // && !('lineNumber' in locator)){
33545          locator.lineNumber = 0;
33546        }
33547      },
33548      //LexicalHandler
33549      comment: function comment(chars, start, length) {
33550        chars = _toString.apply(this, arguments);
33551        var comm = this.doc.createComment(chars);
33552        this.locator && position(this.locator, comm);
33553        appendElement(this, comm);
33554      },
33555      startCDATA: function startCDATA() {
33556        //used in characters() methods
33557        this.cdata = true;
33558      },
33559      endCDATA: function endCDATA() {
33560        this.cdata = false;
33561      },
33562      startDTD: function startDTD(name, publicId, systemId) {
33563        var impl = this.doc.implementation;
33564
33565        if (impl && impl.createDocumentType) {
33566          var dt = impl.createDocumentType(name, publicId, systemId);
33567          this.locator && position(this.locator, dt);
33568          appendElement(this, dt);
33569        }
33570      },
33571
33572      /**
33573       * @see org.xml.sax.ErrorHandler
33574       * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
33575       */
33576      warning: function warning(error) {
33577        console.warn('[xmldom warning]\t' + error, _locator(this.locator));
33578      },
33579      error: function error(_error) {
33580        console.error('[xmldom error]\t' + _error, _locator(this.locator));
33581      },
33582      fatalError: function fatalError(error) {
33583        console.error('[xmldom fatalError]\t' + error, _locator(this.locator));
33584        throw error;
33585      }
33586    };
33587
33588    function _locator(l) {
33589      if (l) {
33590        return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']';
33591      }
33592    }
33593
33594    function _toString(chars, start, length) {
33595      if (typeof chars == 'string') {
33596        return chars.substr(start, length);
33597      } else {
33598        //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
33599        if (chars.length >= start + length || start) {
33600          return new java.lang.String(chars, start, length) + '';
33601        }
33602
33603        return chars;
33604      }
33605    }
33606    /*
33607     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
33608     * used method of org.xml.sax.ext.LexicalHandler:
33609     *  #comment(chars, start, length)
33610     *  #startCDATA()
33611     *  #endCDATA()
33612     *  #startDTD(name, publicId, systemId)
33613     *
33614     *
33615     * IGNORED method of org.xml.sax.ext.LexicalHandler:
33616     *  #endDTD()
33617     *  #startEntity(name)
33618     *  #endEntity(name)
33619     *
33620     *
33621     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
33622     * IGNORED method of org.xml.sax.ext.DeclHandler
33623     * 	#attributeDecl(eName, aName, type, mode, value)
33624     *  #elementDecl(name, model)
33625     *  #externalEntityDecl(name, publicId, systemId)
33626     *  #internalEntityDecl(name, value)
33627     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
33628     * IGNORED method of org.xml.sax.EntityResolver2
33629     *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
33630     *  #resolveEntity(publicId, systemId)
33631     *  #getExternalSubset(name, baseURI)
33632     * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
33633     * IGNORED method of org.xml.sax.DTDHandler
33634     *  #notationDecl(name, publicId, systemId) {};
33635     *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
33636     */
33637
33638
33639    "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) {
33640      DOMHandler.prototype[key] = function () {
33641        return null;
33642      };
33643    });
33644    /* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */
33645
33646    function appendElement(hander, node) {
33647      if (!hander.currentElement) {
33648        hander.doc.appendChild(node);
33649      } else {
33650        hander.currentElement.appendChild(node);
33651      }
33652    } //appendChild and setAttributeNS are preformance key
33653    //if(typeof require == 'function'){
33654
33655
33656    var XMLReader = sax.XMLReader;
33657    var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation;
33658    exports.XMLSerializer = dom.XMLSerializer;
33659    exports.DOMParser = DOMParser; //}
33660  });
33661  var domParser_1 = domParser.DOMImplementation;
33662  var domParser_2 = domParser.XMLSerializer;
33663  var domParser_3 = domParser.DOMParser;
33664
vendor: 22,867 bytes, lines 33665-34357
33665  /*! @name mpd-parser @version 0.14.0 @license Apache-2.0 */
33666
33667  var isObject$1 = function isObject(obj) {
33668    return !!obj && typeof obj === 'object';
33669  };
33670
33671  var merge = function merge() {
33672    for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
33673      objects[_key] = arguments[_key];
33674    }
33675
33676    return objects.reduce(function (result, source) {
33677      Object.keys(source).forEach(function (key) {
33678        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
33679          result[key] = result[key].concat(source[key]);
33680        } else if (isObject$1(result[key]) && isObject$1(source[key])) {
33681          result[key] = merge(result[key], source[key]);
33682        } else {
33683          result[key] = source[key];
33684        }
33685      });
33686      return result;
33687    }, {});
33688  };
33689
33690  var values = function values(o) {
33691    return Object.keys(o).map(function (k) {
33692      return o[k];
33693    });
33694  };
33695
33696  var range = function range(start, end) {
33697    var result = [];
33698
33699    for (var i = start; i < end; i++) {
33700      result.push(i);
33701    }
33702
33703    return result;
33704  };
33705
33706  var flatten = function flatten(lists) {
33707    return lists.reduce(function (x, y) {
33708      return x.concat(y);
33709    }, []);
33710  };
33711
33712  var from = function from(list) {
33713    if (!list.length) {
33714      return [];
33715    }
33716
33717    var result = [];
33718
33719    for (var i = 0; i < list.length; i++) {
33720      result.push(list[i]);
33721    }
33722
33723    return result;
33724  };
33725
33726  var findIndexes = function findIndexes(l, key) {
33727    return l.reduce(function (a, e, i) {
33728      if (e[key]) {
33729        a.push(i);
33730      }
33731
33732      return a;
33733    }, []);
33734  };
33735
33736  var errors = {
33737    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
33738    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
33739    DASH_INVALID_XML: 'DASH_INVALID_XML',
33740    NO_BASE_URL: 'NO_BASE_URL',
33741    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
33742    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
33743    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
33744  };
33745  /**
33746   * @typedef {Object} SingleUri
33747   * @property {string} uri - relative location of segment
33748   * @property {string} resolvedUri - resolved location of segment
33749   * @property {Object} byterange - Object containing information on how to make byte range
33750   *   requests following byte-range-spec per RFC2616.
33751   * @property {String} byterange.length - length of range request
33752   * @property {String} byterange.offset - byte offset of range request
33753   *
33754   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
33755   */
33756
33757  /**
33758   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
33759   * that conforms to how m3u8-parser is structured
33760   *
33761   * @see https://github.com/videojs/m3u8-parser
33762   *
33763   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
33764   * @param {string} source - source url for segment
33765   * @param {string} range - optional range used for range calls,
33766   *   follows  RFC 2616, Clause 14.35.1
33767   * @return {SingleUri} full segment information transformed into a format similar
33768   *   to m3u8-parser
33769   */
33770
33771  var urlTypeToSegment = function urlTypeToSegment(_ref) {
33772    var _ref$baseUrl = _ref.baseUrl,
33773        baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
33774        _ref$source = _ref.source,
33775        source = _ref$source === void 0 ? '' : _ref$source,
33776        _ref$range = _ref.range,
33777        range = _ref$range === void 0 ? '' : _ref$range,
33778        _ref$indexRange = _ref.indexRange,
33779        indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange;
33780    var segment = {
33781      uri: source,
33782      resolvedUri: resolveUrl_1(baseUrl || '', source)
33783    };
33784
33785    if (range || indexRange) {
33786      var rangeStr = range ? range : indexRange;
33787      var ranges = rangeStr.split('-');
33788      var startRange = parseInt(ranges[0], 10);
33789      var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
33790      // RFC 2616, Clause 14.35.1
33791
33792      segment.byterange = {
33793        length: endRange - startRange + 1,
33794        offset: startRange
33795      };
33796    }
33797
33798    return segment;
33799  };
33800
33801  var byteRangeToString = function byteRangeToString(byterange) {
33802    // `endRange` is one less than `offset + length` because the HTTP range
33803    // header uses inclusive ranges
33804    var endRange = byterange.offset + byterange.length - 1;
33805    return byterange.offset + "-" + endRange;
33806  };
33807  /**
33808   * Functions for calculating the range of available segments in static and dynamic
33809   * manifests.
33810   */
33811
33812
33813  var segmentRange = {
33814    /**
33815     * Returns the entire range of available segments for a static MPD
33816     *
33817     * @param {Object} attributes
33818     *        Inheritied MPD attributes
33819     * @return {{ start: number, end: number }}
33820     *         The start and end numbers for available segments
33821     */
33822    "static": function _static(attributes) {
33823      var duration = attributes.duration,
33824          _attributes$timescale = attributes.timescale,
33825          timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
33826          sourceDuration = attributes.sourceDuration;
33827      return {
33828        start: 0,
33829        end: Math.ceil(sourceDuration / (duration / timescale))
33830      };
33831    },
33832
33833    /**
33834     * Returns the current live window range of available segments for a dynamic MPD
33835     *
33836     * @param {Object} attributes
33837     *        Inheritied MPD attributes
33838     * @return {{ start: number, end: number }}
33839     *         The start and end numbers for available segments
33840     */
33841    dynamic: function dynamic(attributes) {
33842      var NOW = attributes.NOW,
33843          clientOffset = attributes.clientOffset,
33844          availabilityStartTime = attributes.availabilityStartTime,
33845          _attributes$timescale2 = attributes.timescale,
33846          timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
33847          duration = attributes.duration,
33848          _attributes$start = attributes.start,
33849          start = _attributes$start === void 0 ? 0 : _attributes$start,
33850          _attributes$minimumUp = attributes.minimumUpdatePeriod,
33851          minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
33852          _attributes$timeShift = attributes.timeShiftBufferDepth,
33853          timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
33854      var now = (NOW + clientOffset) / 1000;
33855      var periodStartWC = availabilityStartTime + start;
33856      var periodEndWC = now + minimumUpdatePeriod;
33857      var periodDuration = periodEndWC - periodStartWC;
33858      var segmentCount = Math.ceil(periodDuration * timescale / duration);
33859      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
33860      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
33861      return {
33862        start: Math.max(0, availableStart),
33863        end: Math.min(segmentCount, availableEnd)
33864      };
33865    }
33866  };
33867  /**
33868   * Maps a range of numbers to objects with information needed to build the corresponding
33869   * segment list
33870   *
33871   * @name toSegmentsCallback
33872   * @function
33873   * @param {number} number
33874   *        Number of the segment
33875   * @param {number} index
33876   *        Index of the number in the range list
33877   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
33878   *         Object with segment timing and duration info
33879   */
33880
33881  /**
33882   * Returns a callback for Array.prototype.map for mapping a range of numbers to
33883   * information needed to build the segment list.
33884   *
33885   * @param {Object} attributes
33886   *        Inherited MPD attributes
33887   * @return {toSegmentsCallback}
33888   *         Callback map function
33889   */
33890
33891  var toSegments = function toSegments(attributes) {
33892    return function (number, index) {
33893      var duration = attributes.duration,
33894          _attributes$timescale3 = attributes.timescale,
33895          timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
33896          periodIndex = attributes.periodIndex,
33897          _attributes$startNumb = attributes.startNumber,
33898          startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
33899      return {
33900        number: startNumber + number,
33901        duration: duration / timescale,
33902        timeline: periodIndex,
33903        time: index * duration
33904      };
33905    };
33906  };
33907  /**
33908   * Returns a list of objects containing segment timing and duration info used for
33909   * building the list of segments. This uses the @duration attribute specified
33910   * in the MPD manifest to derive the range of segments.
33911   *
33912   * @param {Object} attributes
33913   *        Inherited MPD attributes
33914   * @return {{number: number, duration: number, time: number, timeline: number}[]}
33915   *         List of Objects with segment timing and duration info
33916   */
33917
33918
33919  var parseByDuration = function parseByDuration(attributes) {
33920    var _attributes$type = attributes.type,
33921        type = _attributes$type === void 0 ? 'static' : _attributes$type,
33922        duration = attributes.duration,
33923        _attributes$timescale4 = attributes.timescale,
33924        timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
33925        sourceDuration = attributes.sourceDuration;
33926
33927    var _segmentRange$type = segmentRange[type](attributes),
33928        start = _segmentRange$type.start,
33929        end = _segmentRange$type.end;
33930
33931    var segments = range(start, end).map(toSegments(attributes));
33932
33933    if (type === 'static') {
33934      var index = segments.length - 1; // final segment may be less than full segment duration
33935
33936      segments[index].duration = sourceDuration - duration / timescale * index;
33937    }
33938
33939    return segments;
33940  };
33941  /**
33942   * Translates SegmentBase into a set of segments.
33943   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
33944   * node should be translated into segment.
33945   *
33946   * @param {Object} attributes
33947   *   Object containing all inherited attributes from parent elements with attribute
33948   *   names as keys
33949   * @return {Object.<Array>} list of segments
33950   */
33951
33952
33953  var segmentsFromBase = function segmentsFromBase(attributes) {
33954    var baseUrl = attributes.baseUrl,
33955        _attributes$initializ = attributes.initialization,
33956        initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ,
33957        sourceDuration = attributes.sourceDuration,
33958        _attributes$indexRang = attributes.indexRange,
33959        indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
33960        duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
33961
33962    if (!baseUrl) {
33963      throw new Error(errors.NO_BASE_URL);
33964    }
33965
33966    var initSegment = urlTypeToSegment({
33967      baseUrl: baseUrl,
33968      source: initialization.sourceURL,
33969      range: initialization.range
33970    });
33971    var segment = urlTypeToSegment({
33972      baseUrl: baseUrl,
33973      source: baseUrl,
33974      indexRange: indexRange
33975    });
33976    segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
33977    // (since SegmentBase is only for one total segment)
33978
33979    if (duration) {
33980      var segmentTimeInfo = parseByDuration(attributes);
33981
33982      if (segmentTimeInfo.length) {
33983        segment.duration = segmentTimeInfo[0].duration;
33984        segment.timeline = segmentTimeInfo[0].timeline;
33985      }
33986    } else if (sourceDuration) {
33987      segment.duration = sourceDuration;
33988      segment.timeline = 0;
33989    } // This is used for mediaSequence
33990
33991
33992    segment.number = 0;
33993    return [segment];
33994  };
33995  /**
33996   * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
33997   * according to the sidx information given.
33998   *
33999   * playlist.sidx has metadadata about the sidx where-as the sidx param
34000   * is the parsed sidx box itself.
34001   *
34002   * @param {Object} playlist the playlist to update the sidx information for
34003   * @param {Object} sidx the parsed sidx box
34004   * @return {Object} the playlist object with the updated sidx information
34005   */
34006
34007
34008  var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) {
34009    // Retain init segment information
34010    var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing
34011
34012    var sourceDuration = playlist.sidx.duration; // Retain source timeline
34013
34014    var timeline = playlist.timeline || 0;
34015    var sidxByteRange = playlist.sidx.byterange;
34016    var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
34017
34018    var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
34019
34020    var mediaReferences = sidx.references.filter(function (r) {
34021      return r.referenceType !== 1;
34022    });
34023    var segments = []; // firstOffset is the offset from the end of the sidx box
34024
34025    var startIndex = sidxEnd + sidx.firstOffset;
34026
34027    for (var i = 0; i < mediaReferences.length; i++) {
34028      var reference = sidx.references[i]; // size of the referenced (sub)segment
34029
34030      var size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
34031      // this will be converted to seconds when generating segments
34032
34033      var duration = reference.subsegmentDuration; // should be an inclusive range
34034
34035      var endIndex = startIndex + size - 1;
34036      var indexRange = startIndex + "-" + endIndex;
34037      var attributes = {
34038        baseUrl: baseUrl,
34039        timescale: timescale,
34040        timeline: timeline,
34041        // this is used in parseByDuration
34042        periodIndex: timeline,
34043        duration: duration,
34044        sourceDuration: sourceDuration,
34045        indexRange: indexRange
34046      };
34047      var segment = segmentsFromBase(attributes)[0];
34048
34049      if (initSegment) {
34050        segment.map = initSegment;
34051      }
34052
34053      segments.push(segment);
34054      startIndex += size;
34055    }
34056
34057    playlist.segments = segments;
34058    return playlist;
34059  };
34060
34061  var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
34062    var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
34063      // assuming playlist IDs are the same across periods
34064      // TODO: handle multiperiod where representation sets are not the same
34065      // across periods
34066      var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
34067
34068      if (acc[name]) {
34069        var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
34070
34071
34072        if (playlist.segments[0]) {
34073          playlist.segments[0].discontinuity = true;
34074        }
34075
34076        (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
34077        // has the same contentProtection
34078
34079
34080        if (playlist.attributes.contentProtection) {
34081          acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
34082        }
34083      } else {
34084        // first Period
34085        acc[name] = playlist;
34086      }
34087
34088      return acc;
34089    }, {}));
34090    return mergedPlaylists.map(function (playlist) {
34091      playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
34092      return playlist;
34093    });
34094  };
34095
34096  var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) {
34097    if (sidxMapping === void 0) {
34098      sidxMapping = {};
34099    }
34100
34101    if (!Object.keys(sidxMapping).length) {
34102      return playlists;
34103    }
34104
34105    for (var i in playlists) {
34106      var playlist = playlists[i];
34107
34108      if (!playlist.sidx) {
34109        continue;
34110      }
34111
34112      var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange);
34113      var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
34114
34115      if (playlist.sidx && sidxMatch) {
34116        addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
34117      }
34118    }
34119
34120    return playlists;
34121  };
34122
34123  var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
34124    var _attributes;
34125
34126    var attributes = _ref.attributes,
34127        segments = _ref.segments,
34128        sidx = _ref.sidx;
34129    var playlist = {
34130      attributes: (_attributes = {
34131        NAME: attributes.id,
34132        BANDWIDTH: attributes.bandwidth,
34133        CODECS: attributes.codecs
34134      }, _attributes['PROGRAM-ID'] = 1, _attributes),
34135      uri: '',
34136      endList: (attributes.type || 'static') === 'static',
34137      timeline: attributes.periodIndex,
34138      resolvedUri: '',
34139      targetDuration: attributes.duration,
34140      segments: segments,
34141      mediaSequence: segments.length ? segments[0].number : 1
34142    };
34143
34144    if (attributes.contentProtection) {
34145      playlist.contentProtection = attributes.contentProtection;
34146    }
34147
34148    if (sidx) {
34149      playlist.sidx = sidx;
34150    }
34151
34152    return playlist;
34153  };
34154
34155  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
34156    var _m3u8Attributes;
34157
34158    var attributes = _ref2.attributes,
34159        segments = _ref2.segments;
34160
34161    if (typeof segments === 'undefined') {
34162      // vtt tracks may use single file in BaseURL
34163      segments = [{
34164        uri: attributes.baseUrl,
34165        timeline: attributes.periodIndex,
34166        resolvedUri: attributes.baseUrl || '',
34167        duration: attributes.sourceDuration,
34168        number: 0
34169      }]; // targetDuration should be the same duration as the only segment
34170
34171      attributes.duration = attributes.sourceDuration;
34172    }
34173
34174    var m3u8Attributes = (_m3u8Attributes = {
34175      NAME: attributes.id,
34176      BANDWIDTH: attributes.bandwidth
34177    }, _m3u8Attributes['PROGRAM-ID'] = 1, _m3u8Attributes);
34178
34179    if (attributes.codecs) {
34180      m3u8Attributes.CODECS = attributes.codecs;
34181    }
34182
34183    return {
34184      attributes: m3u8Attributes,
34185      uri: '',
34186      endList: (attributes.type || 'static') === 'static',
34187      timeline: attributes.periodIndex,
34188      resolvedUri: attributes.baseUrl || '',
34189      targetDuration: attributes.duration,
34190      segments: segments,
34191      mediaSequence: segments.length ? segments[0].number : 1
34192    };
34193  };
34194
34195  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) {
34196    if (sidxMapping === void 0) {
34197      sidxMapping = {};
34198    }
34199
34200    var mainPlaylist;
34201    var formattedPlaylists = playlists.reduce(function (a, playlist) {
34202      var role = playlist.attributes.role && playlist.attributes.role.value || '';
34203      var language = playlist.attributes.lang || '';
34204      var label = 'main';
34205
34206      if (language) {
34207        var roleLabel = role ? " (" + role + ")" : '';
34208        label = "" + playlist.attributes.lang + roleLabel;
34209      } // skip if we already have the highest quality audio for a language
34210
34211
34212      if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
34213        return a;
34214      }
34215
34216      a[label] = {
34217        language: language,
34218        autoselect: true,
34219        "default": role === 'main',
34220        playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping),
34221        uri: ''
34222      };
34223
34224      if (typeof mainPlaylist === 'undefined' && role === 'main') {
34225        mainPlaylist = playlist;
34226        mainPlaylist["default"] = true;
34227      }
34228
34229      return a;
34230    }, {}); // if no playlists have role "main", mark the first as main
34231
34232    if (!mainPlaylist) {
34233      var firstLabel = Object.keys(formattedPlaylists)[0];
34234      formattedPlaylists[firstLabel]["default"] = true;
34235    }
34236
34237    return formattedPlaylists;
34238  };
34239
34240  var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) {
34241    if (sidxMapping === void 0) {
34242      sidxMapping = {};
34243    }
34244
34245    return playlists.reduce(function (a, playlist) {
34246      var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
34247
34248      if (a[label]) {
34249        return a;
34250      }
34251
34252      a[label] = {
34253        language: label,
34254        "default": false,
34255        autoselect: false,
34256        playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping),
34257        uri: ''
34258      };
34259      return a;
34260    }, {});
34261  };
34262
34263  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
34264    var _attributes2;
34265
34266    var attributes = _ref3.attributes,
34267        segments = _ref3.segments,
34268        sidx = _ref3.sidx;
34269    var playlist = {
34270      attributes: (_attributes2 = {
34271        NAME: attributes.id,
34272        AUDIO: 'audio',
34273        SUBTITLES: 'subs',
34274        RESOLUTION: {
34275          width: attributes.width,
34276          height: attributes.height
34277        },
34278        CODECS: attributes.codecs,
34279        BANDWIDTH: attributes.bandwidth
34280      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
34281      uri: '',
34282      endList: (attributes.type || 'static') === 'static',
34283      timeline: attributes.periodIndex,
34284      resolvedUri: '',
34285      targetDuration: attributes.duration,
34286      segments: segments,
34287      mediaSequence: segments.length ? segments[0].number : 1
34288    };
34289
34290    if (attributes.contentProtection) {
34291      playlist.contentProtection = attributes.contentProtection;
34292    }
34293
34294    if (sidx) {
34295      playlist.sidx = sidx;
34296    }
34297
34298    return playlist;
34299  };
34300
34301  var toM3u8 = function toM3u8(dashPlaylists, locations, sidxMapping) {
34302    var _mediaGroups;
34303
34304    if (sidxMapping === void 0) {
34305      sidxMapping = {};
34306    }
34307
34308    if (!dashPlaylists.length) {
34309      return {};
34310    } // grab all master attributes
34311
34312
34313    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
34314        duration = _dashPlaylists$0$attr.sourceDuration,
34315        _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.type,
34316        type = _dashPlaylists$0$attr2 === void 0 ? 'static' : _dashPlaylists$0$attr2,
34317        suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay,
34318        minimumUpdatePeriod = _dashPlaylists$0$attr.minimumUpdatePeriod;
34319
34320    var videoOnly = function videoOnly(_ref4) {
34321      var attributes = _ref4.attributes;
34322      return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
34323    };
34324
34325    var audioOnly = function audioOnly(_ref5) {
34326      var attributes = _ref5.attributes;
34327      return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
34328    };
34329
34330    var vttOnly = function vttOnly(_ref6) {
34331      var attributes = _ref6.attributes;
34332      return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
34333    };
34334
34335    var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
34336    var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
34337    var vttPlaylists = dashPlaylists.filter(vttOnly);
34338    var master = {
34339      allowCache: true,
34340      discontinuityStarts: [],
34341      segments: [],
34342      endList: true,
34343      mediaGroups: (_mediaGroups = {
34344        AUDIO: {},
34345        VIDEO: {}
34346      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
34347      uri: '',
34348      duration: duration,
34349      playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping)
34350    };
34351
34352    if (minimumUpdatePeriod >= 0) {
34353      master.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
34354    }
34355
34356    if (locations) {
34357 
vendor: 12,359 bytes, lines 34357-34696
34357     master.locations = locations;
34358    }
34359
34360    if (type === 'dynamic') {
34361      master.suggestedPresentationDelay = suggestedPresentationDelay;
34362    }
34363
34364    if (audioPlaylists.length) {
34365      master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
34366    }
34367
34368    if (vttPlaylists.length) {
34369      master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
34370    }
34371
34372    return master;
34373  };
34374  /**
34375   * Calculates the R (repetition) value for a live stream (for the final segment
34376   * in a manifest where the r value is negative 1)
34377   *
34378   * @param {Object} attributes
34379   *        Object containing all inherited attributes from parent elements with attribute
34380   *        names as keys
34381   * @param {number} time
34382   *        current time (typically the total time up until the final segment)
34383   * @param {number} duration
34384   *        duration property for the given <S />
34385   *
34386   * @return {number}
34387   *        R value to reach the end of the given period
34388   */
34389
34390
34391  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
34392    var NOW = attributes.NOW,
34393        clientOffset = attributes.clientOffset,
34394        availabilityStartTime = attributes.availabilityStartTime,
34395        _attributes$timescale = attributes.timescale,
34396        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
34397        _attributes$start = attributes.start,
34398        start = _attributes$start === void 0 ? 0 : _attributes$start,
34399        _attributes$minimumUp = attributes.minimumUpdatePeriod,
34400        minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
34401    var now = (NOW + clientOffset) / 1000;
34402    var periodStartWC = availabilityStartTime + start;
34403    var periodEndWC = now + minimumUpdatePeriod;
34404    var periodDuration = periodEndWC - periodStartWC;
34405    return Math.ceil((periodDuration * timescale - time) / duration);
34406  };
34407  /**
34408   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
34409   * timing and duration
34410   *
34411   * @param {Object} attributes
34412   *        Object containing all inherited attributes from parent elements with attribute
34413   *        names as keys
34414   * @param {Object[]} segmentTimeline
34415   *        List of objects representing the attributes of each S element contained within
34416   *
34417   * @return {{number: number, duration: number, time: number, timeline: number}[]}
34418   *         List of Objects with segment timing and duration info
34419   */
34420
34421
34422  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
34423    var _attributes$type = attributes.type,
34424        type = _attributes$type === void 0 ? 'static' : _attributes$type,
34425        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
34426        minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
34427        _attributes$media = attributes.media,
34428        media = _attributes$media === void 0 ? '' : _attributes$media,
34429        sourceDuration = attributes.sourceDuration,
34430        _attributes$timescale2 = attributes.timescale,
34431        timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
34432        _attributes$startNumb = attributes.startNumber,
34433        startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
34434        timeline = attributes.periodIndex;
34435    var segments = [];
34436    var time = -1;
34437
34438    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
34439      var S = segmentTimeline[sIndex];
34440      var duration = S.d;
34441      var repeat = S.r || 0;
34442      var segmentTime = S.t || 0;
34443
34444      if (time < 0) {
34445        // first segment
34446        time = segmentTime;
34447      }
34448
34449      if (segmentTime && segmentTime > time) {
34450        // discontinuity
34451        // TODO: How to handle this type of discontinuity
34452        // timeline++ here would treat it like HLS discontuity and content would
34453        // get appended without gap
34454        // E.G.
34455        //  <S t="0" d="1" />
34456        //  <S d="1" />
34457        //  <S d="1" />
34458        //  <S t="5" d="1" />
34459        // would have $Time$ values of [0, 1, 2, 5]
34460        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
34461        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
34462        // does the value of sourceDuration consider this when calculating arbitrary
34463        // negative @r repeat value?
34464        // E.G. Same elements as above with this added at the end
34465        //  <S d="1" r="-1" />
34466        //  with a sourceDuration of 10
34467        // Would the 2 gaps be included in the time duration calculations resulting in
34468        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
34469        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
34470        time = segmentTime;
34471      }
34472
34473      var count = void 0;
34474
34475      if (repeat < 0) {
34476        var nextS = sIndex + 1;
34477
34478        if (nextS === segmentTimeline.length) {
34479          // last segment
34480          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
34481            count = getLiveRValue(attributes, time, duration);
34482          } else {
34483            // TODO: This may be incorrect depending on conclusion of TODO above
34484            count = (sourceDuration * timescale - time) / duration;
34485          }
34486        } else {
34487          count = (segmentTimeline[nextS].t - time) / duration;
34488        }
34489      } else {
34490        count = repeat + 1;
34491      }
34492
34493      var end = startNumber + segments.length + count;
34494      var number = startNumber + segments.length;
34495
34496      while (number < end) {
34497        segments.push({
34498          number: number,
34499          duration: duration / timescale,
34500          time: time,
34501          timeline: timeline
34502        });
34503        time += duration;
34504        number++;
34505      }
34506    }
34507
34508    return segments;
34509  };
34510
34511  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
34512  /**
34513   * Replaces template identifiers with corresponding values. To be used as the callback
34514   * for String.prototype.replace
34515   *
34516   * @name replaceCallback
34517   * @function
34518   * @param {string} match
34519   *        Entire match of identifier
34520   * @param {string} identifier
34521   *        Name of matched identifier
34522   * @param {string} format
34523   *        Format tag string. Its presence indicates that padding is expected
34524   * @param {string} width
34525   *        Desired length of the replaced value. Values less than this width shall be left
34526   *        zero padded
34527   * @return {string}
34528   *         Replacement for the matched identifier
34529   */
34530
34531  /**
34532   * Returns a function to be used as a callback for String.prototype.replace to replace
34533   * template identifiers
34534   *
34535   * @param {Obect} values
34536   *        Object containing values that shall be used to replace known identifiers
34537   * @param {number} values.RepresentationID
34538   *        Value of the Representation@id attribute
34539   * @param {number} values.Number
34540   *        Number of the corresponding segment
34541   * @param {number} values.Bandwidth
34542   *        Value of the Representation@bandwidth attribute.
34543   * @param {number} values.Time
34544   *        Timestamp value of the corresponding segment
34545   * @return {replaceCallback}
34546   *         Callback to be used with String.prototype.replace to replace identifiers
34547   */
34548
34549  var identifierReplacement = function identifierReplacement(values) {
34550    return function (match, identifier, format, width) {
34551      if (match === '$$') {
34552        // escape sequence
34553        return '$';
34554      }
34555
34556      if (typeof values[identifier] === 'undefined') {
34557        return match;
34558      }
34559
34560      var value = '' + values[identifier];
34561
34562      if (identifier === 'RepresentationID') {
34563        // Format tag shall not be present with RepresentationID
34564        return value;
34565      }
34566
34567      if (!format) {
34568        width = 1;
34569      } else {
34570        width = parseInt(width, 10);
34571      }
34572
34573      if (value.length >= width) {
34574        return value;
34575      }
34576
34577      return "" + new Array(width - value.length + 1).join('0') + value;
34578    };
34579  };
34580  /**
34581   * Constructs a segment url from a template string
34582   *
34583   * @param {string} url
34584   *        Template string to construct url from
34585   * @param {Obect} values
34586   *        Object containing values that shall be used to replace known identifiers
34587   * @param {number} values.RepresentationID
34588   *        Value of the Representation@id attribute
34589   * @param {number} values.Number
34590   *        Number of the corresponding segment
34591   * @param {number} values.Bandwidth
34592   *        Value of the Representation@bandwidth attribute.
34593   * @param {number} values.Time
34594   *        Timestamp value of the corresponding segment
34595   * @return {string}
34596   *         Segment url with identifiers replaced
34597   */
34598
34599
34600  var constructTemplateUrl = function constructTemplateUrl(url, values) {
34601    return url.replace(identifierPattern, identifierReplacement(values));
34602  };
34603  /**
34604   * Generates a list of objects containing timing and duration information about each
34605   * segment needed to generate segment uris and the complete segment object
34606   *
34607   * @param {Object} attributes
34608   *        Object containing all inherited attributes from parent elements with attribute
34609   *        names as keys
34610   * @param {Object[]|undefined} segmentTimeline
34611   *        List of objects representing the attributes of each S element contained within
34612   *        the SegmentTimeline element
34613   * @return {{number: number, duration: number, time: number, timeline: number}[]}
34614   *         List of Objects with segment timing and duration info
34615   */
34616
34617
34618  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
34619    if (!attributes.duration && !segmentTimeline) {
34620      // if neither @duration or SegmentTimeline are present, then there shall be exactly
34621      // one media segment
34622      return [{
34623        number: attributes.startNumber || 1,
34624        duration: attributes.sourceDuration,
34625        time: 0,
34626        timeline: attributes.periodIndex
34627      }];
34628    }
34629
34630    if (attributes.duration) {
34631      return parseByDuration(attributes);
34632    }
34633
34634    return parseByTimeline(attributes, segmentTimeline);
34635  };
34636  /**
34637   * Generates a list of segments using information provided by the SegmentTemplate element
34638   *
34639   * @param {Object} attributes
34640   *        Object containing all inherited attributes from parent elements with attribute
34641   *        names as keys
34642   * @param {Object[]|undefined} segmentTimeline
34643   *        List of objects representing the attributes of each S element contained within
34644   *        the SegmentTimeline element
34645   * @return {Object[]}
34646   *         List of segment objects
34647   */
34648
34649
34650  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
34651    var templateValues = {
34652      RepresentationID: attributes.id,
34653      Bandwidth: attributes.bandwidth || 0
34654    };
34655    var _attributes$initializ = attributes.initialization,
34656        initialization = _attributes$initializ === void 0 ? {
34657      sourceURL: '',
34658      range: ''
34659    } : _attributes$initializ;
34660    var mapSegment = urlTypeToSegment({
34661      baseUrl: attributes.baseUrl,
34662      source: constructTemplateUrl(initialization.sourceURL, templateValues),
34663      range: initialization.range
34664    });
34665    var segments = parseTemplateInfo(attributes, segmentTimeline);
34666    return segments.map(function (segment) {
34667      templateValues.Number = segment.number;
34668      templateValues.Time = segment.time;
34669      var uri = constructTemplateUrl(attributes.media || '', templateValues);
34670      return {
34671        uri: uri,
34672        timeline: segment.timeline,
34673        duration: segment.duration,
34674        resolvedUri: resolveUrl_1(attributes.baseUrl || '', uri),
34675        map: mapSegment,
34676        number: segment.number
34677      };
34678    });
34679  };
34680  /**
34681   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
34682   * to an object that matches the output of a segment in videojs/mpd-parser
34683   *
34684   * @param {Object} attributes
34685   *   Object containing all inherited attributes from parent elements with attribute
34686   *   names as keys
34687   * @param {Object} segmentUrl
34688   *   <SegmentURL> node to translate into a segment object
34689   * @return {Object} translated segment object
34690   */
34691
34692
34693  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
34694    var baseUrl = attributes.baseUrl,
34695        _attributes$initializ = attributes.initialization,
34696        initialization = _attributes$initializ === void 0 ? {}
vendor: 15,650 bytes, lines 34696-35181
34696 : _attributes$initializ;
34697    var initSegment = urlTypeToSegment({
34698      baseUrl: baseUrl,
34699      source: initialization.sourceURL,
34700      range: initialization.range
34701    });
34702    var segment = urlTypeToSegment({
34703      baseUrl: baseUrl,
34704      source: segmentUrl.media,
34705      range: segmentUrl.mediaRange
34706    });
34707    segment.map = initSegment;
34708    return segment;
34709  };
34710  /**
34711   * Generates a list of segments using information provided by the SegmentList element
34712   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
34713   * node should be translated into segment.
34714   *
34715   * @param {Object} attributes
34716   *   Object containing all inherited attributes from parent elements with attribute
34717   *   names as keys
34718   * @param {Object[]|undefined} segmentTimeline
34719   *        List of objects representing the attributes of each S element contained within
34720   *        the SegmentTimeline element
34721   * @return {Object.<Array>} list of segments
34722   */
34723
34724
34725  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
34726    var duration = attributes.duration,
34727        _attributes$segmentUr = attributes.segmentUrls,
34728        segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
34729    // if both SegmentTimeline and @duration are defined, it is outside of spec.
34730
34731    if (!duration && !segmentTimeline || duration && segmentTimeline) {
34732      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
34733    }
34734
34735    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
34736      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
34737    });
34738    var segmentTimeInfo;
34739
34740    if (duration) {
34741      segmentTimeInfo = parseByDuration(attributes);
34742    }
34743
34744    if (segmentTimeline) {
34745      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
34746    }
34747
34748    var segments = segmentTimeInfo.map(function (segmentTime, index) {
34749      if (segmentUrlMap[index]) {
34750        var segment = segmentUrlMap[index];
34751        segment.timeline = segmentTime.timeline;
34752        segment.duration = segmentTime.duration;
34753        segment.number = segmentTime.number;
34754        return segment;
34755      } // Since we're mapping we should get rid of any blank segments (in case
34756      // the given SegmentTimeline is handling for more elements than we have
34757      // SegmentURLs for).
34758
34759    }).filter(function (segment) {
34760      return segment;
34761    });
34762    return segments;
34763  };
34764
34765  var generateSegments = function generateSegments(_ref) {
34766    var attributes = _ref.attributes,
34767        segmentInfo = _ref.segmentInfo;
34768    var segmentAttributes;
34769    var segmentsFn;
34770
34771    if (segmentInfo.template) {
34772      segmentsFn = segmentsFromTemplate;
34773      segmentAttributes = merge(attributes, segmentInfo.template);
34774    } else if (segmentInfo.base) {
34775      segmentsFn = segmentsFromBase;
34776      segmentAttributes = merge(attributes, segmentInfo.base);
34777    } else if (segmentInfo.list) {
34778      segmentsFn = segmentsFromList;
34779      segmentAttributes = merge(attributes, segmentInfo.list);
34780    }
34781
34782    var segmentsInfo = {
34783      attributes: attributes
34784    };
34785
34786    if (!segmentsFn) {
34787      return segmentsInfo;
34788    }
34789
34790    var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
34791    // must be in seconds. Since we've generated the segment list, we no longer need
34792    // @duration to be in @timescale units, so we can convert it here.
34793
34794    if (segmentAttributes.duration) {
34795      var _segmentAttributes = segmentAttributes,
34796          duration = _segmentAttributes.duration,
34797          _segmentAttributes$ti = _segmentAttributes.timescale,
34798          timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
34799      segmentAttributes.duration = duration / timescale;
34800    } else if (segments.length) {
34801      // if there is no @duration attribute, use the largest segment duration as
34802      // as target duration
34803      segmentAttributes.duration = segments.reduce(function (max, segment) {
34804        return Math.max(max, Math.ceil(segment.duration));
34805      }, 0);
34806    } else {
34807      segmentAttributes.duration = 0;
34808    }
34809
34810    segmentsInfo.attributes = segmentAttributes;
34811    segmentsInfo.segments = segments; // This is a sidx box without actual segment information
34812
34813    if (segmentInfo.base && segmentAttributes.indexRange) {
34814      segmentsInfo.sidx = segments[0];
34815      segmentsInfo.segments = [];
34816    }
34817
34818    return segmentsInfo;
34819  };
34820
34821  var toPlaylists = function toPlaylists(representations) {
34822    return representations.map(generateSegments);
34823  };
34824
34825  var findChildren = function findChildren(element, name) {
34826    return from(element.childNodes).filter(function (_ref) {
34827      var tagName = _ref.tagName;
34828      return tagName === name;
34829    });
34830  };
34831
34832  var getContent = function getContent(element) {
34833    return element.textContent.trim();
34834  };
34835
34836  var parseDuration = function parseDuration(str) {
34837    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
34838    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
34839    var SECONDS_IN_DAY = 24 * 60 * 60;
34840    var SECONDS_IN_HOUR = 60 * 60;
34841    var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
34842
34843    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
34844    var match = durationRegex.exec(str);
34845
34846    if (!match) {
34847      return 0;
34848    }
34849
34850    var _match$slice = match.slice(1),
34851        year = _match$slice[0],
34852        month = _match$slice[1],
34853        day = _match$slice[2],
34854        hour = _match$slice[3],
34855        minute = _match$slice[4],
34856        second = _match$slice[5];
34857
34858    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);
34859  };
34860
34861  var parseDate = function parseDate(str) {
34862    // Date format without timezone according to ISO 8601
34863    // YYY-MM-DDThh:mm:ss.ssssss
34864    var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
34865    // expressed by ending with 'Z'
34866
34867    if (dateRegex.test(str)) {
34868      str += 'Z';
34869    }
34870
34871    return Date.parse(str);
34872  };
34873
34874  var parsers = {
34875    /**
34876     * Specifies the duration of the entire Media Presentation. Format is a duration string
34877     * as specified in ISO 8601
34878     *
34879     * @param {string} value
34880     *        value of attribute as a string
34881     * @return {number}
34882     *         The duration in seconds
34883     */
34884    mediaPresentationDuration: function mediaPresentationDuration(value) {
34885      return parseDuration(value);
34886    },
34887
34888    /**
34889     * Specifies the Segment availability start time for all Segments referred to in this
34890     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
34891     * time. Format is a date string as specified in ISO 8601
34892     *
34893     * @param {string} value
34894     *        value of attribute as a string
34895     * @return {number}
34896     *         The date as seconds from unix epoch
34897     */
34898    availabilityStartTime: function availabilityStartTime(value) {
34899      return parseDate(value) / 1000;
34900    },
34901
34902    /**
34903     * Specifies the smallest period between potential changes to the MPD. Format is a
34904     * duration string as specified in ISO 8601
34905     *
34906     * @param {string} value
34907     *        value of attribute as a string
34908     * @return {number}
34909     *         The duration in seconds
34910     */
34911    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
34912      return parseDuration(value);
34913    },
34914
34915    /**
34916     * Specifies the suggested presentation delay. Format is a
34917     * duration string as specified in ISO 8601
34918     *
34919     * @param {string} value
34920     *        value of attribute as a string
34921     * @return {number}
34922     *         The duration in seconds
34923     */
34924    suggestedPresentationDelay: function suggestedPresentationDelay(value) {
34925      return parseDuration(value);
34926    },
34927
34928    /**
34929     * specifices the type of mpd. Can be either "static" or "dynamic"
34930     *
34931     * @param {string} value
34932     *        value of attribute as a string
34933     *
34934     * @return {string}
34935     *         The type as a string
34936     */
34937    type: function type(value) {
34938      return value;
34939    },
34940
34941    /**
34942     * Specifies the duration of the smallest time shifting buffer for any Representation
34943     * in the MPD. Format is a duration string as specified in ISO 8601
34944     *
34945     * @param {string} value
34946     *        value of attribute as a string
34947     * @return {number}
34948     *         The duration in seconds
34949     */
34950    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
34951      return parseDuration(value);
34952    },
34953
34954    /**
34955     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
34956     * Format is a duration string as specified in ISO 8601
34957     *
34958     * @param {string} value
34959     *        value of attribute as a string
34960     * @return {number}
34961     *         The duration in seconds
34962     */
34963    start: function start(value) {
34964      return parseDuration(value);
34965    },
34966
34967    /**
34968     * Specifies the width of the visual presentation
34969     *
34970     * @param {string} value
34971     *        value of attribute as a string
34972     * @return {number}
34973     *         The parsed width
34974     */
34975    width: function width(value) {
34976      return parseInt(value, 10);
34977    },
34978
34979    /**
34980     * Specifies the height of the visual presentation
34981     *
34982     * @param {string} value
34983     *        value of attribute as a string
34984     * @return {number}
34985     *         The parsed height
34986     */
34987    height: function height(value) {
34988      return parseInt(value, 10);
34989    },
34990
34991    /**
34992     * Specifies the bitrate of the representation
34993     *
34994     * @param {string} value
34995     *        value of attribute as a string
34996     * @return {number}
34997     *         The parsed bandwidth
34998     */
34999    bandwidth: function bandwidth(value) {
35000      return parseInt(value, 10);
35001    },
35002
35003    /**
35004     * Specifies the number of the first Media Segment in this Representation in the Period
35005     *
35006     * @param {string} value
35007     *        value of attribute as a string
35008     * @return {number}
35009     *         The parsed number
35010     */
35011    startNumber: function startNumber(value) {
35012      return parseInt(value, 10);
35013    },
35014
35015    /**
35016     * Specifies the timescale in units per seconds
35017     *
35018     * @param {string} value
35019     *        value of attribute as a string
35020     * @return {number}
35021     *         The aprsed timescale
35022     */
35023    timescale: function timescale(value) {
35024      return parseInt(value, 10);
35025    },
35026
35027    /**
35028     * Specifies the constant approximate Segment duration
35029     * NOTE: The <Period> element also contains an @duration attribute. This duration
35030     *       specifies the duration of the Period. This attribute is currently not
35031     *       supported by the rest of the parser, however we still check for it to prevent
35032     *       errors.
35033     *
35034     * @param {string} value
35035     *        value of attribute as a string
35036     * @return {number}
35037     *         The parsed duration
35038     */
35039    duration: function duration(value) {
35040      var parsedValue = parseInt(value, 10);
35041
35042      if (isNaN(parsedValue)) {
35043        return parseDuration(value);
35044      }
35045
35046      return parsedValue;
35047    },
35048
35049    /**
35050     * Specifies the Segment duration, in units of the value of the @timescale.
35051     *
35052     * @param {string} value
35053     *        value of attribute as a string
35054     * @return {number}
35055     *         The parsed duration
35056     */
35057    d: function d(value) {
35058      return parseInt(value, 10);
35059    },
35060
35061    /**
35062     * Specifies the MPD start time, in @timescale units, the first Segment in the series
35063     * starts relative to the beginning of the Period
35064     *
35065     * @param {string} value
35066     *        value of attribute as a string
35067     * @return {number}
35068     *         The parsed time
35069     */
35070    t: function t(value) {
35071      return parseInt(value, 10);
35072    },
35073
35074    /**
35075     * Specifies the repeat count of the number of following contiguous Segments with the
35076     * same duration expressed by the value of @d
35077     *
35078     * @param {string} value
35079     *        value of attribute as a string
35080     * @return {number}
35081     *         The parsed number
35082     */
35083    r: function r(value) {
35084      return parseInt(value, 10);
35085    },
35086
35087    /**
35088     * Default parser for all other attributes. Acts as a no-op and just returns the value
35089     * as a string
35090     *
35091     * @param {string} value
35092     *        value of attribute as a string
35093     * @return {string}
35094     *         Unparsed value
35095     */
35096    DEFAULT: function DEFAULT(value) {
35097      return value;
35098    }
35099  };
35100  /**
35101   * Gets all the attributes and values of the provided node, parses attributes with known
35102   * types, and returns an object with attribute names mapped to values.
35103   *
35104   * @param {Node} el
35105   *        The node to parse attributes from
35106   * @return {Object}
35107   *         Object with all attributes of el parsed
35108   */
35109
35110  var parseAttributes$1 = function parseAttributes(el) {
35111    if (!(el && el.attributes)) {
35112      return {};
35113    }
35114
35115    return from(el.attributes).reduce(function (a, e) {
35116      var parseFn = parsers[e.name] || parsers.DEFAULT;
35117      a[e.name] = parseFn(e.value);
35118      return a;
35119    }, {});
35120  };
35121
35122  var keySystemsMap = {
35123    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
35124    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
35125    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
35126    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
35127  };
35128  /**
35129   * Builds a list of urls that is the product of the reference urls and BaseURL values
35130   *
35131   * @param {string[]} referenceUrls
35132   *        List of reference urls to resolve to
35133   * @param {Node[]} baseUrlElements
35134   *        List of BaseURL nodes from the mpd
35135   * @return {string[]}
35136   *         List of resolved urls
35137   */
35138
35139  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
35140    if (!baseUrlElements.length) {
35141      return referenceUrls;
35142    }
35143
35144    return flatten(referenceUrls.map(function (reference) {
35145      return baseUrlElements.map(function (baseUrlElement) {
35146        return resolveUrl_1(reference, getContent(baseUrlElement));
35147      });
35148    }));
35149  };
35150  /**
35151   * Contains all Segment information for its containing AdaptationSet
35152   *
35153   * @typedef {Object} SegmentInformation
35154   * @property {Object|undefined} template
35155   *           Contains the attributes for the SegmentTemplate node
35156   * @property {Object[]|undefined} timeline
35157   *           Contains a list of atrributes for each S node within the SegmentTimeline node
35158   * @property {Object|undefined} list
35159   *           Contains the attributes for the SegmentList node
35160   * @property {Object|undefined} base
35161   *           Contains the attributes for the SegmentBase node
35162   */
35163
35164  /**
35165   * Returns all available Segment information contained within the AdaptationSet node
35166   *
35167   * @param {Node} adaptationSet
35168   *        The AdaptationSet node to get Segment information from
35169   * @return {SegmentInformation}
35170   *         The Segment information contained within the provided AdaptationSet
35171   */
35172
35173
35174  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
35175    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
35176    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
35177    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
35178      return merge({
35179        tag: 'SegmentURL'
35180      }, parseAttributes$1(s));
35181    });
vendor: 2,523 bytes, lines 35182-35236
35182    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
35183    var segmentTimelineParentNode = segmentList || segmentTemplate;
35184    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
35185    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
35186    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
35187    // @initialization and an <Initialization> node.  @initialization can be templated,
35188    // while the node can have a url and range specified.  If the <SegmentTemplate> has
35189    // both @initialization and an <Initialization> subelement we opt to override with
35190    // the node, as this interaction is not defined in the spec.
35191
35192    var template = segmentTemplate && parseAttributes$1(segmentTemplate);
35193
35194    if (template && segmentInitialization) {
35195      template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
35196    } else if (template && template.initialization) {
35197      // If it is @initialization we convert it to an object since this is the format that
35198      // later functions will rely on for the initialization segment.  This is only valid
35199      // for <SegmentTemplate>
35200      template.initialization = {
35201        sourceURL: template.initialization
35202      };
35203    }
35204
35205    var segmentInfo = {
35206      template: template,
35207      timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
35208        return parseAttributes$1(s);
35209      }),
35210      list: segmentList && merge(parseAttributes$1(segmentList), {
35211        segmentUrls: segmentUrls,
35212        initialization: parseAttributes$1(segmentInitialization)
35213      }),
35214      base: segmentBase && merge(parseAttributes$1(segmentBase), {
35215        initialization: parseAttributes$1(segmentInitialization)
35216      })
35217    };
35218    Object.keys(segmentInfo).forEach(function (key) {
35219      if (!segmentInfo[key]) {
35220        delete segmentInfo[key];
35221      }
35222    });
35223    return segmentInfo;
35224  };
35225  /**
35226   * Contains Segment information and attributes needed to construct a Playlist object
35227   * from a Representation
35228   *
35229   * @typedef {Object} RepresentationInformation
35230   * @property {SegmentInformation} segmentInfo
35231   *           Segment information for this Representation
35232   * @property {Object} attributes
35233   *           Inherited attributes for this Representation
35234   */
35235
35236  /**
35237   * Maps a Representation node to an object containing Segment information and attributes
35238   *
35239   * @name inheritBaseUrlsCallback
35240   * @function
35241   * @param {Node} representation
35242   *        Representation node from the mpd
35243   * @return {RepresentationInformation}
35244   *         Representation information needed to construct a Playlist object
35245   */
35246
35247  /**
35248   * Returns a callback for Array.prototype.map for mapping Representation nodes to
35249   * Segment information and attributes using inherited BaseURL nodes.
35250   *
35251   * @param {Object} adaptationSetAttributes
35252   *        Contains attributes inherited by the AdaptationSet
35253   * @param {string[]} adaptationSetBaseUrls
35254   *        Contains list of resolved base urls inherited by the AdaptationSet
35255   * @param {SegmentInformation} adaptationSetSegmentInfo
35256   *        Contains Segment information for the AdaptationSet
35257   * @return {inheritBaseUrlsCallback}
35258   *         Callback map function
35259   */
35260
35261
35262  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
35263    return function (representation) {
35264      var repBaseUrlElements = findChildren(representation, 'BaseURL');
35265      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
35266      var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
35267      var representationSegmentInfo = getSegmentInformation(representation);
35268      return repBaseUrls.map(function (baseUrl) {
35269        return {
35270          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
35271          attributes: merge(attributes, {
35272            baseUrl: baseUrl
35273          })
35274        };
35275      });
35276    };
35277  };
35278  /**
35279   * Tranforms a series of content protection nodes to
35280   * an object containing pssh data by key system
35281   *
35282   * @param {Node[]} contentProtectionNodes
35283   *        Content protection nodes
35284   * @return {Object}
35285   *        Object containing pssh data by key system
35286   */
35287
35288
35289  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
35290    return contentProtectionNodes.reduce(function (acc, node) {
35291      var attributes = parseAttributes$1(node);
35292      var keySystem = keySystemsMap[attributes.schemeIdUri];
35293
35294      if (keySystem) {
35295        acc[keySystem] = {
35296          attributes: attributes
35297        };
35298        var psshNode = findChildren(node, 'cenc:pssh')[0];
35299
35300        if (psshNode) {
35301          var pssh = getContent(psshNode);
35302          var psshBuffer = pssh && decodeB64ToUint8Array_1(pssh);
35303          acc[keySystem].pssh = psshBuffer;
35304        }
35305      }
35306
35307      return acc;
35308    }, {});
35309  };
35310  /**
35311   * Maps an AdaptationSet node to a list of Representation information objects
35312   *
35313   * @name toRepresentationsCallback
35314   * @function
35315   * @param {Node} adaptationSet
35316   *        AdaptationSet node from the mpd
35317   * @return {RepresentationInformation[]}
35318   *         List of objects containing Representaion information
35319   */
35320
35321  /**
35322   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
35323   * Representation information objects
35324   *
35325   * @param {Object} periodAttributes
35326   *        Contains attributes inherited by the Period
35327   * @param {string[]} periodBaseUrls
35328   *        Contains list of resolved base urls inherited by the Period
35329   * @param {string[]} periodSegmentInfo
35330   *        Contains Segment Information at the period level
35331   * @return {toRepresentationsCallback}
35332   *         Callback map function
35333   */
35334
35335
35336  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
35337    return function (adaptationSet) {
35338      var adaptationSetAttributes = parseAttributes$1(adaptationSet);
35339      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
35340      var role = findChildren(adaptationSet, 'Role')[0];
35341      var roleAttributes = {
35342        role: parseAttributes$1(role)
35343      };
35344      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
35345      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
35346
35347      if (Object.keys(contentProtection).length) {
35348        attrs = merge(attrs, {
35349          contentProtection: contentProtection
35350        });
35351      }
35352
35353      var segmentInfo = getSegmentInformation(adaptationSet);
35354      var representations = findChildren(adaptationSet, 'Representation');
35355      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
35356      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
35357    };
35358  };
35359  /**
35360   * Maps an Period node to a list of Representation inforamtion objects for all
35361   * AdaptationSet nodes contained within the Period
35362   *
35363   * @name toAdaptationSetsCallback
35364   * @function
35365   * @param {Node} period
35366   *        Period node from the mpd
35367   * @param {number} periodIndex
35368   *        Index of the Period within the mpd
35369   * @return {RepresentationInformation[]}
35370   *         List of objects containing Representaion information
35371   */
35372
35373  /**
35374   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
35375   * Representation information objects
35376   *
35377   * @param {Object} mpdAttributes
35378   *        Contains attributes inherited by the mpd
35379   * @param {string[]} mpdBaseUrls
35380   *        Contains list of resolved base urls inherited by the mpd
35381   * @return {toAdaptationSetsCallback}
35382   *         Callback map function
35383   */
35384
35385
35386  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
35387    return function (period, index) {
35388      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
35389      var periodAtt = parseAttributes$1(period);
35390      var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
35391
35392      var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
35393      var periodAttributes = merge(mpdAttributes, {
35394        periodIndex: periodIndex
35395      });
35396      var adaptationSets = findChildren(period, 'AdaptationSet');
35397      var periodSegmentInfo = getSegmentInformation(period);
35398      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
35399    };
35400  };
35401  /**
35402   * Traverses the mpd xml tree to generate a list of Representation information objects
35403   * that have inherited attributes from parent nodes
35404   *
35405   * @param {Node} mpd
35406   *        The root node of the mpd
35407   * @param {Object} options
35408   *        Available options for inheritAttributes
35409   * @param {string} options.manifestUri
35410   *        The uri source of the mpd
35411   * @param {number} options.NOW
35412   *        Current time per DASH IOP.  Default is current time in ms since epoch
35413   * @param {number} options.clientOffset
35414   *        Client time difference from NOW (in milliseconds)
35415   * @return {RepresentationInformation[]}
35416   *         List of objects containing Representation information
35417   */
35418
35419
35420  var inheritAttributes = function inheritAttributes(mpd, options) {
35421    if (options === void 0) {
35422      options = {};
35423    }
35424
35425    var _options = options,
35426        _options$manifestUri = _options.manifestUri,
35427        manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
35428        _options$NOW = _options.NOW,
35429        NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
35430        _options$clientOffset = _options.clientOffset,
35431        clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
vendor: 4,425 bytes, lines 35432-35571
35432    var periods = findChildren(mpd, 'Period');
35433
35434    if (!periods.length) {
35435      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
35436    }
35437
35438    var locations = findChildren(mpd, 'Location');
35439    var mpdAttributes = parseAttributes$1(mpd);
35440    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
35441    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
35442    mpdAttributes.NOW = NOW;
35443    mpdAttributes.clientOffset = clientOffset;
35444
35445    if (locations.length) {
35446      mpdAttributes.locations = locations.map(getContent);
35447    }
35448
35449    return {
35450      locations: mpdAttributes.locations,
35451      representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)))
35452    };
35453  };
35454
35455  var stringToMpdXml = function stringToMpdXml(manifestString) {
35456    if (manifestString === '') {
35457      throw new Error(errors.DASH_EMPTY_MANIFEST);
35458    }
35459
35460    var parser = new domParser_3();
35461    var xml = parser.parseFromString(manifestString, 'application/xml');
35462    var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
35463
35464    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
35465      throw new Error(errors.DASH_INVALID_XML);
35466    }
35467
35468    return mpd;
35469  };
35470  /**
35471   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
35472   *
35473   * @param {string} mpd
35474   *        XML string of the MPD manifest
35475   * @return {Object|null}
35476   *         Attributes of UTCTiming node specified in the manifest. Null if none found
35477   */
35478
35479
35480  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
35481    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
35482
35483    if (!UTCTimingNode) {
35484      return null;
35485    }
35486
35487    var attributes = parseAttributes$1(UTCTimingNode);
35488
35489    switch (attributes.schemeIdUri) {
35490      case 'urn:mpeg:dash:utc:http-head:2014':
35491      case 'urn:mpeg:dash:utc:http-head:2012':
35492        attributes.method = 'HEAD';
35493        break;
35494
35495      case 'urn:mpeg:dash:utc:http-xsdate:2014':
35496      case 'urn:mpeg:dash:utc:http-iso:2014':
35497      case 'urn:mpeg:dash:utc:http-xsdate:2012':
35498      case 'urn:mpeg:dash:utc:http-iso:2012':
35499        attributes.method = 'GET';
35500        break;
35501
35502      case 'urn:mpeg:dash:utc:direct:2014':
35503      case 'urn:mpeg:dash:utc:direct:2012':
35504        attributes.method = 'DIRECT';
35505        attributes.value = Date.parse(attributes.value);
35506        break;
35507
35508      case 'urn:mpeg:dash:utc:http-ntp:2014':
35509      case 'urn:mpeg:dash:utc:ntp:2014':
35510      case 'urn:mpeg:dash:utc:sntp:2014':
35511      default:
35512        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
35513    }
35514
35515    return attributes;
35516  };
35517
35518  var parse = function parse(manifestString, options) {
35519    if (options === void 0) {
35520      options = {};
35521    }
35522
35523    var parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
35524    var playlists = toPlaylists(parsedManifestInfo.representationInfo);
35525    return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping);
35526  };
35527  /**
35528   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
35529   *
35530   * @param {string} manifestString
35531   *        XML string of the MPD manifest
35532   * @return {Object|null}
35533   *         Attributes of UTCTiming node specified in the manifest. Null if none found
35534   */
35535
35536
35537  var parseUTCTiming = function parseUTCTiming(manifestString) {
35538    return parseUTCTimingScheme(stringToMpdXml(manifestString));
35539  };
35540
35541  var parseSidx = function parseSidx(data) {
35542    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
35543        result = {
35544      version: data[0],
35545      flags: new Uint8Array(data.subarray(1, 4)),
35546      references: [],
35547      referenceId: view.getUint32(4),
35548      timescale: view.getUint32(8),
35549      earliestPresentationTime: view.getUint32(12),
35550      firstOffset: view.getUint32(16)
35551    },
35552        referenceCount = view.getUint16(22),
35553        i;
35554
35555    for (i = 24; referenceCount; i += 12, referenceCount--) {
35556      result.references.push({
35557        referenceType: (data[i] & 0x80) >>> 7,
35558        referencedSize: view.getUint32(i) & 0x7FFFFFFF,
35559        subsegmentDuration: view.getUint32(i + 4),
35560        startsWithSap: !!(data[i + 8] & 0x80),
35561        sapType: (data[i + 8] & 0x70) >>> 4,
35562        sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
35563      });
35564    }
35565
35566    return result;
35567  };
35568
35569  var parseSidx_1 = parseSidx;
35570
35571  var containers = createCommonjsModule(function (module, exports
35571) {
35572
35573    Object.defineProperty(exports, '__esModule', {
35574      value: true
35575    });
35576
35577    var toUint8 = function toUint8(bytes) {
35578      return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
35579    };
35580
35581    var bytesToString = function bytesToString(bytes) {
35582      if (!bytes) {
35583        return '';
35584      }
35585
35586      bytes = Array.prototype.slice.call(bytes);
35587      var string = String.fromCharCode.apply(null, toUint8(bytes));
35588
35589      try {
35590        return decodeURIComponent(escape(string));
35591      } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial
35592        // or full non string data. Just return the potentially garbled string.
35593      }
35594
35595      return string;
35596    };
35597
35598    var id3Size = function id3Size(bytes, offset) {
35599      if (offset === void 0) {
35600        offset = 0;
35601      }
35602
35603      bytes = toUint8(bytes);
35604      var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
35605      var flags = bytes[offset + 5];
35606      var footerPresent = (flags & 16) >> 4;
35607
35608      if (footerPresent) {
35609        return returnSize + 20;
35610      }
35611
35612      return returnSize + 10;
35613    };
35614
35615    var getId3Offset = function getId3Offset(bytes, offset) {
35616      if (offset === void 0) {
35617        offset = 0;
35618      }
35619
35620      bytes = toUint8(bytes);
35621
35622      if (bytes.length - offset < 10 || bytesToString(bytes.subarray(offset, offset + 3)) !== 'ID3') {
35623        return offset;
35624      }
35625
35626      offset += id3Size(bytes, offset); // recursive check for id3 tags as some files
35627      // have multiple ID3 tag sections even though
35628      // they should not.
35629
35630      return getId3Offset(bytes, offset);
35631    };
35632
35633    var isLikely = {
35634      aac: function aac(bytes) {
35635        var offset = getId3Offset(bytes);
35636        return bytes.length >= offset + 2 && (bytes[offset] & 0xFF) === 0xFF && (bytes[offset + 1] & 0xE0) === 0xE0 && (bytes[offset + 1] & 0x16) === 0x10;
35637      },
35638      mp3: function mp3(bytes) {
35639        var offset = getId3Offset(bytes);
35640        return bytes.length >= offset + 2 && (bytes[offset] & 0xFF) === 0xFF && (bytes[offset + 1] & 0xE0) === 0xE0 && (bytes[offset + 1] & 0x06) === 0x02;
35641      },
35642      webm: function webm(bytes) {
35643        return bytes.length >= 4 && (bytes[0] & 0xFF) === 0x1A && (bytes[1] & 0xFF) === 0x45 && (bytes[2] & 0xFF) === 0xDF && (bytes[3] & 0xFF) === 0xA3;
35644      },
35645      mp4: function mp4(bytes) {
35646        return bytes.length >= 8 && /^(f|s)typ$/.test(bytesToString(bytes.subarray(4, 8))) && // not 3gp data
35647        !/^ftyp3g$/.test(bytesToString(bytes.subarray(4, 10)));
35648      },
35649      '3gp': function gp(bytes) {
35650        return bytes.length >= 10 && /^ftyp3g$/.test(bytesToString(bytes.subarray(4, 10)));
35651      },
35652      ts: function ts(bytes) {
35653        if (bytes.length < 189 && bytes.length >= 1) {
35654          return bytes[0] === 0x47;
35655        }
35656
35657        var i = 0; // check the first 376 bytes for two matching sync bytes
35658
35659        while (i + 188 < bytes.length && i < 188) {
35660          if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
35661            return true;
35662          }
35663
35664          i += 1;
35665        }
35666
35667        return false;
35668      },
35669      flac: function flac(bytes) {
35670        return bytes.length >= 4 && /^fLaC$/.test(bytesToString(bytes.subarray(0, 4)));
35671      },
35672      ogg: function ogg(bytes) {
35673        return bytes.length >= 4 && /^OggS$/.test(bytesToString(bytes.subarray(0, 4)));
35674      }
35675    }; // get all the isLikely functions
35676    // but make sure 'ts' is at the bottom
35677    // as it is the least specific
35678
35679    var isLikelyTypes = Object.keys(isLikely) // remove ts
35680    .filter(function (t) {
35681      return t !== 'ts';
35682    }) // add it back to the bottom
35683    .concat('ts'); // make sure we are dealing with uint8 data.
35684
35685    isLikelyTypes.forEach(function (type) {
35686      var isLikelyFn = isLikely[type];
35687
35688      isLikely[type] = function (bytes) {
35689        return isLikelyFn(toUint8(bytes));
35690      };
35691    }); // A useful list of file signatures can be found here
35692    // https://en.wikipedia.org/wiki/List_of_file_signatures
35693
35694    var detectContainerForBytes = function detectContainerForBytes(bytes) {
35695      bytes = toUint8(bytes);
35696
35697      for (var i = 0; i < isLikelyTypes.length; i++) {
35698        var type = isLikelyTypes[i];
35699
35700        if (isLikely[type](bytes)) {
35701          return type;
35702        }
35703      }
35704
35705      return '';
35706    }; // fmp4 is not a container
35707
35708
35709    var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
35710      bytes = toUint8(bytes);
35711      var i = 0;
35712
35713      while (i < bytes.length) {
35714        var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
35715        var type = bytesToString(bytes.subarray(i + 4, i + 8));
35716
35717        if (type === 'moof') {
35718          return true;
35719        }
35720
35721        if (size === 0 || size + i > bytes.length) {
35722          i = bytes.length;
35723        } else {
35724          i += size;
35725        }
35726      }
35727
35728      return false;
35729    };
35730
35731    exports.detectContainerForBytes = detectContainerForBytes;
35732    exports.getId3Offset = getId3Offset;
35733    exports.id3Size = id3Size;
35734    exports.isLikely = isLikely;
35735    exports.isLikelyFmp4MediaSegment = isLikelyFmp4MediaSegment;
35736  });
35737  unwrapExports(containers);
35738  var containers_1 = containers.detectContainerForBytes;
35739  var containers_2 = containers.getId3Offset;
35740  var containers_3 = containers.id3Size;
35741  var containers_4 = containers.isLikely;
35742  var containers_5 = containers.isLikelyFmp4MediaSegment;
35743
35744  var byteHelpers = createCommonjsModule(function (module, exports) {
35745
35746    Object.defineProperty(exports, '__esModule', {
35747      value: true
35748    });
35749
35750    var isTypedArray = function isTypedArray(obj) {
35751      return ArrayBuffer.isView(obj);
35752    };
35753
35754    var toUint8 = function toUint8(bytes) {
35755      return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
35756    };
35757
35758    var bytesToString = function bytesToString(bytes) {
35759      if (!bytes) {
35760        return '';
35761      }
35762
35763      bytes = Array.prototype.slice.call(bytes);
35764      var string = String.fromCharCode.apply(null, toUint8(bytes));
35765
35766      try {
35767        return decodeURIComponent(escape(string));
35768      } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial
35769        // or full non string data. Just return the potentially garbled string.
35770      }
35771
35772      return string;
35773    };
35774
35775    var stringToBytes = function stringToBytes(string, stringIsBytes) {
35776      if (stringIsBytes === void 0) {
35777        stringIsBytes = false;
35778      }
35779
35780      var bytes = [];
35781
35782      if (typeof string !== 'string' && string && typeof string.toString === 'function') {
35783        string = string.toString();
35784      }
35785
35786      if (typeof string !== 'string') {
35787        return bytes;
35788      } // If the string already is bytes, we don't have to do this
35789
35790
35791      if (!stringIsBytes) {
vendor: 5,499 bytes, lines 35792-35973
35792        string = unescape(encodeURIComponent(string));
35793      }
35794
35795      return string.split('').map(function (s) {
35796        return s.charCodeAt(0) & 0xFF;
35797      });
35798    };
35799
35800    var concatTypedArrays = function concatTypedArrays() {
35801      for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
35802        buffers[_key] = arguments[_key];
35803      }
35804
35805      var totalLength = buffers.reduce(function (total, buf) {
35806        var len = buf && (buf.byteLength || buf.length);
35807        total += len || 0;
35808        return total;
35809      }, 0);
35810      var tempBuffer = new Uint8Array(totalLength);
35811      var offset = 0;
35812      buffers.forEach(function (buf) {
35813        buf = toUint8(buf);
35814        tempBuffer.set(buf, offset);
35815        offset += buf.byteLength;
35816      });
35817      return tempBuffer;
35818    };
35819
35820    exports.bytesToString = bytesToString;
35821    exports.concatTypedArrays = concatTypedArrays;
35822    exports.isTypedArray = isTypedArray;
35823    exports.stringToBytes = stringToBytes;
35824    exports.toUint8 = toUint8;
35825  });
35826  unwrapExports(byteHelpers);
35827  var byteHelpers_1 = byteHelpers.bytesToString;
35828  var byteHelpers_2 = byteHelpers.concatTypedArrays;
35829  var byteHelpers_3 = byteHelpers.isTypedArray;
35830  var byteHelpers_4 = byteHelpers.stringToBytes;
35831  var byteHelpers_5 = byteHelpers.toUint8;
35832
35833  /**
35834   * mux.js
35835   *
35836   * Copyright (c) Brightcove
35837   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35838   */
35839
35840  var streamTypes = {
35841    H264_STREAM_TYPE: 0x1B,
35842    ADTS_STREAM_TYPE: 0x0F,
35843    METADATA_STREAM_TYPE: 0x15
35844  };
35845
35846  /**
35847   * mux.js
35848   *
35849   * Copyright (c) Brightcove
35850   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35851   *
35852   * A lightweight readable stream implemention that handles event dispatching.
35853   * Objects that inherit from streams should call init in their constructors.
35854   */
35855
35856  var Stream$1 = function Stream() {
35857    this.init = function () {
35858      var listeners = {};
35859      /**
35860       * Add a listener for a specified event type.
35861       * @param type {string} the event name
35862       * @param listener {function} the callback to be invoked when an event of
35863       * the specified type occurs
35864       */
35865
35866      this.on = function (type, listener) {
35867        if (!listeners[type]) {
35868          listeners[type] = [];
35869        }
35870
35871        listeners[type] = listeners[type].concat(listener);
35872      };
35873      /**
35874       * Remove a listener for a specified event type.
35875       * @param type {string} the event name
35876       * @param listener {function} a function previously registered for this
35877       * type of event through `on`
35878       */
35879
35880
35881      this.off = function (type, listener) {
35882        var index;
35883
35884        if (!listeners[type]) {
35885          return false;
35886        }
35887
35888        index = listeners[type].indexOf(listener);
35889        listeners[type] = listeners[type].slice();
35890        listeners[type].splice(index, 1);
35891        return index > -1;
35892      };
35893      /**
35894       * Trigger an event of the specified type on this stream. Any additional
35895       * arguments to this function are passed as parameters to event listeners.
35896       * @param type {string} the event name
35897       */
35898
35899
35900      this.trigger = function (type) {
35901        var callbacks, i, length, args;
35902        callbacks = listeners[type];
35903
35904        if (!callbacks) {
35905          return;
35906        } // Slicing the arguments on every invocation of this method
35907        // can add a significant amount of overhead. Avoid the
35908        // intermediate object creation for the common case of a
35909        // single callback argument
35910
35911
35912        if (arguments.length === 2) {
35913          length = callbacks.length;
35914
35915          for (i = 0; i < length; ++i) {
35916            callbacks[i].call(this, arguments[1]);
35917          }
35918        } else {
35919          args = [];
35920          i = arguments.length;
35921
35922          for (i = 1; i < arguments.length; ++i) {
35923            args.push(arguments[i]);
35924          }
35925
35926          length = callbacks.length;
35927
35928          for (i = 0; i < length; ++i) {
35929            callbacks[i].apply(this, args);
35930          }
35931        }
35932      };
35933      /**
35934       * Destroys the stream and cleans up.
35935       */
35936
35937
35938      this.dispose = function () {
35939        listeners = {};
35940      };
35941    };
35942  };
35943  /**
35944   * Forwards all `data` events on this stream to the destination stream. The
35945   * destination stream should provide a method `push` to receive the data
35946   * events as they arrive.
35947   * @param destination {stream} the stream that will receive all `data` events
35948   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
35949   *                            when the current stream emits a 'done' event
35950   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
35951   */
35952
35953
35954  Stream$1.prototype.pipe = function (destination) {
35955    this.on('data', function (data) {
35956      destination.push(data);
35957    });
35958    this.on('done', function (flushSource) {
35959      destination.flush(flushSource);
35960    });
35961    this.on('partialdone', function (flushSource) {
35962      destination.partialFlush(flushSource);
35963    });
35964    this.on('endedtimeline', function (flushSource) {
35965      destination.endTimeline(flushSource);
35966    });
35967    this.on('reset', function (flushSource) {
35968      destination.reset(flushSource);
35969    });
35970    return destination;
35971  }; // Default stream functions that are expected to be overridden to perform
35972  // actual work. These are provided by the prototype as a sort of no-op
35973  // implementation so that we don't have to check for their existence in the
vendor: 40,390 bytes, lines 35974-37305
35974  // `pipe` function above.
35975
35976
35977  Stream$1.prototype.push = function (data) {
35978    this.trigger('data', data);
35979  };
35980
35981  Stream$1.prototype.flush = function (flushSource) {
35982    this.trigger('done', flushSource);
35983  };
35984
35985  Stream$1.prototype.partialFlush = function (flushSource) {
35986    this.trigger('partialdone', flushSource);
35987  };
35988
35989  Stream$1.prototype.endTimeline = function (flushSource) {
35990    this.trigger('endedtimeline', flushSource);
35991  };
35992
35993  Stream$1.prototype.reset = function (flushSource) {
35994    this.trigger('reset', flushSource);
35995  };
35996
35997  var stream$1 = Stream$1;
35998
35999  var MAX_TS = 8589934592;
36000  var RO_THRESH = 4294967296;
36001  var TYPE_SHARED = 'shared';
36002
36003  var handleRollover = function handleRollover(value, reference) {
36004    var direction = 1;
36005
36006    if (value > reference) {
36007      // If the current timestamp value is greater than our reference timestamp and we detect a
36008      // timestamp rollover, this means the roll over is happening in the opposite direction.
36009      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
36010      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
36011      // rollover point. In loading this segment, the timestamp values will be very large,
36012      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
36013      // the time stamp to be `value - 2^33`.
36014      direction = -1;
36015    } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
36016    // cause an incorrect adjustment.
36017
36018
36019    while (Math.abs(reference - value) > RO_THRESH) {
36020      value += direction * MAX_TS;
36021    }
36022
36023    return value;
36024  };
36025
36026  var TimestampRolloverStream = function TimestampRolloverStream(type) {
36027    var lastDTS, referenceDTS;
36028    TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
36029    // video and audio. We could use `undefined` here, but having a string
36030    // makes debugging a little clearer.
36031
36032    this.type_ = type || TYPE_SHARED;
36033
36034    this.push = function (data) {
36035      // Any "shared" rollover streams will accept _all_ data. Otherwise,
36036      // streams will only accept data that matches their type.
36037      if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
36038        return;
36039      }
36040
36041      if (referenceDTS === undefined) {
36042        referenceDTS = data.dts;
36043      }
36044
36045      data.dts = handleRollover(data.dts, referenceDTS);
36046      data.pts = handleRollover(data.pts, referenceDTS);
36047      lastDTS = data.dts;
36048      this.trigger('data', data);
36049    };
36050
36051    this.flush = function () {
36052      referenceDTS = lastDTS;
36053      this.trigger('done');
36054    };
36055
36056    this.endTimeline = function () {
36057      this.flush();
36058      this.trigger('endedtimeline');
36059    };
36060
36061    this.discontinuity = function () {
36062      referenceDTS = void 0;
36063      lastDTS = void 0;
36064    };
36065
36066    this.reset = function () {
36067      this.discontinuity();
36068      this.trigger('reset');
36069    };
36070  };
36071
36072  TimestampRolloverStream.prototype = new stream$1();
36073  var timestampRolloverStream = {
36074    TimestampRolloverStream: TimestampRolloverStream,
36075    handleRollover: handleRollover
36076  };
36077
36078  var parsePid = function parsePid(packet) {
36079    var pid = packet[1] & 0x1f;
36080    pid <<= 8;
36081    pid |= packet[2];
36082    return pid;
36083  };
36084
36085  var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
36086    return !!(packet[1] & 0x40);
36087  };
36088
36089  var parseAdaptionField = function parseAdaptionField(packet) {
36090    var offset = 0; // if an adaption field is present, its length is specified by the
36091    // fifth byte of the TS packet header. The adaptation field is
36092    // used to add stuffing to PES packets that don't fill a complete
36093    // TS packet, and to specify some forms of timing and control data
36094    // that we do not currently use.
36095
36096    if ((packet[3] & 0x30) >>> 4 > 0x01) {
36097      offset += packet[4] + 1;
36098    }
36099
36100    return offset;
36101  };
36102
36103  var parseType = function parseType(packet, pmtPid) {
36104    var pid = parsePid(packet);
36105
36106    if (pid === 0) {
36107      return 'pat';
36108    } else if (pid === pmtPid) {
36109      return 'pmt';
36110    } else if (pmtPid) {
36111      return 'pes';
36112    }
36113
36114    return null;
36115  };
36116
36117  var parsePat = function parsePat(packet) {
36118    var pusi = parsePayloadUnitStartIndicator(packet);
36119    var offset = 4 + parseAdaptionField(packet);
36120
36121    if (pusi) {
36122      offset += packet[offset] + 1;
36123    }
36124
36125    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
36126  };
36127
36128  var parsePmt = function parsePmt(packet) {
36129    var programMapTable = {};
36130    var pusi = parsePayloadUnitStartIndicator(packet);
36131    var payloadOffset = 4 + parseAdaptionField(packet);
36132
36133    if (pusi) {
36134      payloadOffset += packet[payloadOffset] + 1;
36135    } // PMTs can be sent ahead of the time when they should actually
36136    // take effect. We don't believe this should ever be the case
36137    // for HLS but we'll ignore "forward" PMT declarations if we see
36138    // them. Future PMT declarations have the current_next_indicator
36139    // set to zero.
36140
36141
36142    if (!(packet[payloadOffset + 5] & 0x01)) {
36143      return;
36144    }
36145
36146    var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
36147
36148    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
36149    tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
36150    // long the program info descriptors are
36151
36152    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
36153
36154    var offset = 12 + programInfoLength;
36155
36156    while (offset < tableEnd) {
36157      var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
36158
36159      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
36160      // skip past the elementary stream descriptors, if present
36161
36162      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
36163    }
36164
36165    return programMapTable;
36166  };
36167
36168  var parsePesType = function parsePesType(packet, programMapTable) {
36169    var pid = parsePid(packet);
36170    var type = programMapTable[pid];
36171
36172    switch (type) {
36173      case streamTypes.H264_STREAM_TYPE:
36174        return 'video';
36175
36176      case streamTypes.ADTS_STREAM_TYPE:
36177        return 'audio';
36178
36179      case streamTypes.METADATA_STREAM_TYPE:
36180        return 'timed-metadata';
36181
36182      default:
36183        return null;
36184    }
36185  };
36186
36187  var parsePesTime = function parsePesTime(packet) {
36188    var pusi = parsePayloadUnitStartIndicator(packet);
36189
36190    if (!pusi) {
36191      return null;
36192    }
36193
36194    var offset = 4 + parseAdaptionField(packet);
36195
36196    if (offset >= packet.byteLength) {
36197      // From the H 222.0 MPEG-TS spec
36198      // "For transport stream packets carrying PES packets, stuffing is needed when there
36199      //  is insufficient PES packet data to completely fill the transport stream packet
36200      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
36201      //  the sum of the lengths of the data elements in it, so that the payload bytes
36202      //  remaining after the adaptation field exactly accommodates the available PES packet
36203      //  data."
36204      //
36205      // If the offset is >= the length of the packet, then the packet contains no data
36206      // and instead is just adaption field stuffing bytes
36207      return null;
36208    }
36209
36210    var pes = null;
36211    var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
36212    // and a DTS value. Determine what combination of values is
36213    // available to work with.
36214
36215    ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
36216    // performs all bitwise operations on 32-bit integers but javascript
36217    // supports a much greater range (52-bits) of integer using standard
36218    // mathematical operations.
36219    // We construct a 31-bit value using bitwise operators over the 31
36220    // most significant bits and then multiply by 4 (equal to a left-shift
36221    // of 2) before we add the final 2 least significant bits of the
36222    // timestamp (equal to an OR.)
36223
36224    if (ptsDtsFlags & 0xC0) {
36225      pes = {}; // the PTS and DTS are not written out directly. For information
36226      // on how they are encoded, see
36227      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
36228
36229      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;
36230      pes.pts *= 4; // Left shift by 2
36231
36232      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
36233
36234      pes.dts = pes.pts;
36235
36236      if (ptsDtsFlags & 0x40) {
36237        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;
36238        pes.dts *= 4; // Left shift by 2
36239
36240        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
36241      }
36242    }
36243
36244    return pes;
36245  };
36246
36247  var parseNalUnitType = function parseNalUnitType(type) {
36248    switch (type) {
36249      case 0x05:
36250        return 'slice_layer_without_partitioning_rbsp_idr';
36251
36252      case 0x06:
36253        return 'sei_rbsp';
36254
36255      case 0x07:
36256        return 'seq_parameter_set_rbsp';
36257
36258      case 0x08:
36259        return 'pic_parameter_set_rbsp';
36260
36261      case 0x09:
36262        return 'access_unit_delimiter_rbsp';
36263
36264      default:
36265        return null;
36266    }
36267  };
36268
36269  var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
36270    var offset = 4 + parseAdaptionField(packet);
36271    var frameBuffer = packet.subarray(offset);
36272    var frameI = 0;
36273    var frameSyncPoint = 0;
36274    var foundKeyFrame = false;
36275    var nalType; // advance the sync point to a NAL start, if necessary
36276
36277    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
36278      if (frameBuffer[frameSyncPoint + 2] === 1) {
36279        // the sync point is properly aligned
36280        frameI = frameSyncPoint + 5;
36281        break;
36282      }
36283    }
36284
36285    while (frameI < frameBuffer.byteLength) {
36286      // look at the current byte to determine if we've hit the end of
36287      // a NAL unit boundary
36288      switch (frameBuffer[frameI]) {
36289        case 0:
36290          // skip past non-sync sequences
36291          if (frameBuffer[frameI - 1] !== 0) {
36292            frameI += 2;
36293            break;
36294          } else if (frameBuffer[frameI - 2] !== 0) {
36295            frameI++;
36296            break;
36297          }
36298
36299          if (frameSyncPoint + 3 !== frameI - 2) {
36300            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
36301
36302            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
36303              foundKeyFrame = true;
36304            }
36305          } // drop trailing zeroes
36306
36307
36308          do {
36309            frameI++;
36310          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
36311
36312          frameSyncPoint = frameI - 2;
36313          frameI += 3;
36314          break;
36315
36316        case 1:
36317          // skip past non-sync sequences
36318          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
36319            frameI += 3;
36320            break;
36321          }
36322
36323          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
36324
36325          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
36326            foundKeyFrame = true;
36327          }
36328
36329          frameSyncPoint = frameI - 2;
36330          frameI += 3;
36331          break;
36332
36333        default:
36334          // the current byte isn't a one or zero, so it cannot be part
36335          // of a sync sequence
36336          frameI += 3;
36337          break;
36338      }
36339    }
36340
36341    frameBuffer = frameBuffer.subarray(frameSyncPoint);
36342    frameI -= frameSyncPoint;
36343    frameSyncPoint = 0; // parse the final nal
36344
36345    if (frameBuffer && frameBuffer.byteLength > 3) {
36346      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
36347
36348      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
36349        foundKeyFrame = true;
36350      }
36351    }
36352
36353    return foundKeyFrame;
36354  };
36355
36356  var probe = {
36357    parseType: parseType,
36358    parsePat: parsePat,
36359    parsePmt: parsePmt,
36360    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
36361    parsePesType: parsePesType,
36362    parsePesTime: parsePesTime,
36363    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
36364  };
36365
36366  /**
36367   * mux.js
36368   *
36369   * Copyright (c) Brightcove
36370   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
36371   *
36372   * Utilities to detect basic properties and metadata about Aac data.
36373   */
36374
36375  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
36376
36377  var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
36378    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
36379        flags = header[byteIndex + 5],
36380        footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
36381
36382    returnSize = returnSize >= 0 ? returnSize : 0;
36383
36384    if (footerPresent) {
36385      return returnSize + 20;
36386    }
36387
36388    return returnSize + 10;
36389  };
36390
36391  var getId3Offset = function getId3Offset(data, offset) {
36392    if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
36393      return offset;
36394    }
36395
36396    offset += parseId3TagSize(data, offset);
36397    return getId3Offset(data, offset);
36398  }; // TODO: use vhs-utils
36399
36400
36401  var isLikelyAacData = function isLikelyAacData(data) {
36402    var offset = getId3Offset(data, 0);
36403    return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this
36404    // is not mp3 data but aac data.
36405    (data[offset + 1] & 0x16) === 0x10;
36406  };
36407
36408  var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
36409    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
36410  }; // return a percent-encoded representation of the specified byte range
36411  // @see http://en.wikipedia.org/wiki/Percent-encoding
36412
36413
36414  var percentEncode = function percentEncode(bytes, start, end) {
36415    var i,
36416        result = '';
36417
36418    for (i = start; i < end; i++) {
36419      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
36420    }
36421
36422    return result;
36423  }; // return the string representation of the specified byte range,
36424  // interpreted as ISO-8859-1.
36425
36426
36427  var parseIso88591 = function parseIso88591(bytes, start, end) {
36428    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
36429  };
36430
36431  var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
36432    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
36433        middle = header[byteIndex + 4] << 3,
36434        highTwo = header[byteIndex + 3] & 0x3 << 11;
36435    return highTwo | middle | lowThree;
36436  };
36437
36438  var parseType$1 = function parseType(header, byteIndex) {
36439    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
36440      return 'timed-metadata';
36441    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
36442      return 'audio';
36443    }
36444
36445    return null;
36446  };
36447
36448  var parseSampleRate = function parseSampleRate(packet) {
36449    var i = 0;
36450
36451    while (i + 5 < packet.length) {
36452      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
36453        // If a valid header was not found,  jump one forward and attempt to
36454        // find a valid ADTS header starting at the next byte
36455        i++;
36456        continue;
36457      }
36458
36459      return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
36460    }
36461
36462    return null;
36463  };
36464
36465  var parseAacTimestamp = function parseAacTimestamp(packet) {
36466    var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
36467
36468    frameStart = 10;
36469
36470    if (packet[5] & 0x40) {
36471      // advance the frame start past the extended header
36472      frameStart += 4; // header size field
36473
36474      frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
36475    } // parse one or more ID3 frames
36476    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
36477
36478
36479    do {
36480      // determine the number of bytes in this frame
36481      frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
36482
36483      if (frameSize < 1) {
36484        return null;
36485      }
36486
36487      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
36488
36489      if (frameHeader === 'PRIV') {
36490        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
36491
36492        for (var i = 0; i < frame.byteLength; i++) {
36493          if (frame[i] === 0) {
36494            var owner = parseIso88591(frame, 0, i);
36495
36496            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
36497              var d = frame.subarray(i + 1);
36498              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
36499              size *= 4;
36500              size += d[7] & 0x03;
36501              return size;
36502            }
36503
36504            break;
36505          }
36506        }
36507      }
36508
36509      frameStart += 10; // advance past the frame header
36510
36511      frameStart += frameSize; // advance past the frame body
36512    } while (frameStart < packet.byteLength);
36513
36514    return null;
36515  };
36516
36517  var utils = {
36518    isLikelyAacData: isLikelyAacData,
36519    parseId3TagSize: parseId3TagSize,
36520    parseAdtsSize: parseAdtsSize,
36521    parseType: parseType$1,
36522    parseSampleRate: parseSampleRate,
36523    parseAacTimestamp: parseAacTimestamp
36524  };
36525
36526  /**
36527   * mux.js
36528   *
36529   * Copyright (c) Brightcove
36530   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
36531   */
36532  var ONE_SECOND_IN_TS = 90000,
36533      // 90kHz clock
36534  secondsToVideoTs,
36535      secondsToAudioTs,
36536      videoTsToSeconds,
36537      audioTsToSeconds,
36538      audioTsToVideoTs,
36539      videoTsToAudioTs,
36540      metadataTsToSeconds;
36541
36542  secondsToVideoTs = function secondsToVideoTs(seconds) {
36543    return seconds * ONE_SECOND_IN_TS;
36544  };
36545
36546  secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
36547    return seconds * sampleRate;
36548  };
36549
36550  videoTsToSeconds = function videoTsToSeconds(timestamp) {
36551    return timestamp / ONE_SECOND_IN_TS;
36552  };
36553
36554  audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
36555    return timestamp / sampleRate;
36556  };
36557
36558  audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
36559    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
36560  };
36561
36562  videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
36563    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
36564  };
36565  /**
36566   * Adjust ID3 tag or caption timing information by the timeline pts values
36567   * (if keepOriginalTimestamps is false) and convert to seconds
36568   */
36569
36570
36571  metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
36572    return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
36573  };
36574
36575  var clock = {
36576    ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
36577    secondsToVideoTs: secondsToVideoTs,
36578    secondsToAudioTs: secondsToAudioTs,
36579    videoTsToSeconds: videoTsToSeconds,
36580    audioTsToSeconds: audioTsToSeconds,
36581    audioTsToVideoTs: audioTsToVideoTs,
36582    videoTsToAudioTs: videoTsToAudioTs,
36583    metadataTsToSeconds: metadataTsToSeconds
36584  };
36585  var clock_1 = clock.ONE_SECOND_IN_TS;
36586
36587  var handleRollover$1 = timestampRolloverStream.handleRollover;
36588  var probe$1 = {};
36589  probe$1.ts = probe;
36590  probe$1.aac = utils;
36591  var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
36592  var MP2T_PACKET_LENGTH = 188,
36593      // bytes
36594  SYNC_BYTE = 0x47;
36595  /**
36596   * walks through segment data looking for pat and pmt packets to parse out
36597   * program map table information
36598   */
36599
36600  var parsePsi_ = function parsePsi_(bytes, pmt) {
36601    var startIndex = 0,
36602        endIndex = MP2T_PACKET_LENGTH,
36603        packet,
36604        type;
36605
36606    while (endIndex < bytes.byteLength) {
36607      // Look for a pair of start and end sync bytes in the data..
36608      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36609        // We found a packet
36610        packet = bytes.subarray(startIndex, endIndex);
36611        type = probe$1.ts.parseType(packet, pmt.pid);
36612
36613        switch (type) {
36614          case 'pat':
36615            if (!pmt.pid) {
36616              pmt.pid = probe$1.ts.parsePat(packet);
36617            }
36618
36619            break;
36620
36621          case 'pmt':
36622            if (!pmt.table) {
36623              pmt.table = probe$1.ts.parsePmt(packet);
36624            }
36625
36626            break;
36627        } // Found the pat and pmt, we can stop walking the segment
36628
36629
36630        if (pmt.pid && pmt.table) {
36631          return;
36632        }
36633
36634        startIndex += MP2T_PACKET_LENGTH;
36635        endIndex += MP2T_PACKET_LENGTH;
36636        continue;
36637      } // If we get here, we have somehow become de-synchronized and we need to step
36638      // forward one byte at a time until we find a pair of sync bytes that denote
36639      // a packet
36640
36641
36642      startIndex++;
36643      endIndex++;
36644    }
36645  };
36646  /**
36647   * walks through the segment data from the start and end to get timing information
36648   * for the first and last audio pes packets
36649   */
36650
36651
36652  var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
36653    var startIndex = 0,
36654        endIndex = MP2T_PACKET_LENGTH,
36655        packet,
36656        type,
36657        pesType,
36658        pusi,
36659        parsed;
36660    var endLoop = false; // Start walking from start of segment to get first audio packet
36661
36662    while (endIndex <= bytes.byteLength) {
36663      // Look for a pair of start and end sync bytes in the data..
36664      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
36665        // We found a packet
36666        packet = bytes.subarray(startIndex, endIndex);
36667        type = probe$1.ts.parseType(packet, pmt.pid);
36668
36669        switch (type) {
36670          case 'pes':
36671            pesType = probe$1.ts.parsePesType(packet, pmt.table);
36672            pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet);
36673
36674            if (pesType === 'audio' && pusi) {
36675              parsed = probe$1.ts.parsePesTime(packet);
36676
36677              if (parsed) {
36678                parsed.type = 'audio';
36679                result.audio.push(parsed);
36680                endLoop = true;
36681              }
36682            }
36683
36684            break;
36685        }
36686
36687        if (endLoop) {
36688          break;
36689        }
36690
36691        startIndex += MP2T_PACKET_LENGTH;
36692        endIndex += MP2T_PACKET_LENGTH;
36693        continue;
36694      } // If we get here, we have somehow become de-synchronized and we need to step
36695      // forward one byte at a time until we find a pair of sync bytes that denote
36696      // a packet
36697
36698
36699      startIndex++;
36700      endIndex++;
36701    } // Start walking from end of segment to get last audio packet
36702
36703
36704    endIndex = bytes.byteLength;
36705    startIndex = endIndex - MP2T_PACKET_LENGTH;
36706    endLoop = false;
36707
36708    while (startIndex >= 0) {
36709      // Look for a pair of start and end sync bytes in the data..
36710      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
36711        // We found a packet
36712        packet = bytes.subarray(startIndex, endIndex);
36713        type = probe$1.ts.parseType(packet, pmt.pid);
36714
36715        switch (type) {
36716          case 'pes':
36717            pesType = probe$1.ts.parsePesType(packet, pmt.table);
36718            pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet);
36719
36720            if (pesType === 'audio' && pusi) {
36721              parsed = probe$1.ts.parsePesTime(packet);
36722
36723              if (parsed) {
36724                parsed.type = 'audio';
36725                result.audio.push(parsed);
36726                endLoop = true;
36727              }
36728            }
36729
36730            break;
36731        }
36732
36733        if (endLoop) {
36734          break;
36735        }
36736
36737        startIndex -= MP2T_PACKET_LENGTH;
36738        endIndex -= MP2T_PACKET_LENGTH;
36739        continue;
36740      } // If we get here, we have somehow become de-synchronized and we need to step
36741      // forward one byte at a time until we find a pair of sync bytes that denote
36742      // a packet
36743
36744
36745      startIndex--;
36746      endIndex--;
36747    }
36748  };
36749  /**
36750   * walks through the segment data from the start and end to get timing information
36751   * for the first and last video pes packets as well as timing information for the first
36752   * key frame.
36753   */
36754
36755
36756  var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
36757    var startIndex = 0,
36758        endIndex = MP2T_PACKET_LENGTH,
36759        packet,
36760        type,
36761        pesType,
36762        pusi,
36763        parsed,
36764        frame,
36765        i,
36766        pes;
36767    var endLoop = false;
36768    var currentFrame = {
36769      data: [],
36770      size: 0
36771    }; // Start walking from start of segment to get first video packet
36772
36773    while (endIndex < bytes.byteLength) {
36774      // Look for a pair of start and end sync bytes in the data..
36775      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36776        // We found a packet
36777        packet = bytes.subarray(startIndex, endIndex);
36778        type = probe$1.ts.parseType(packet, pmt.pid);
36779
36780        switch (type) {
36781          case 'pes':
36782            pesType = probe$1.ts.parsePesType(packet, pmt.table);
36783            pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet);
36784
36785            if (pesType === 'video') {
36786              if (pusi && !endLoop) {
36787                parsed = probe$1.ts.parsePesTime(packet);
36788
36789                if (parsed) {
36790                  parsed.type = 'video';
36791                  result.video.push(parsed);
36792                  endLoop = true;
36793                }
36794              }
36795
36796              if (!result.firstKeyFrame) {
36797                if (pusi) {
36798                  if (currentFrame.size !== 0) {
36799                    frame = new Uint8Array(currentFrame.size);
36800                    i = 0;
36801
36802                    while (currentFrame.data.length) {
36803                      pes = currentFrame.data.shift();
36804                      frame.set(pes, i);
36805                      i += pes.byteLength;
36806                    }
36807
36808                    if (probe$1.ts.videoPacketContainsKeyFrame(frame)) {
36809                      var firstKeyFrame = probe$1.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
36810                      // the keyframe seems to work fine with HLS playback
36811                      // and definitely preferable to a crash with TypeError...
36812
36813                      if (firstKeyFrame) {
36814                        result.firstKeyFrame = firstKeyFrame;
36815                        result.firstKeyFrame.type = 'video';
36816                      } else {
36817                        // eslint-disable-next-line
36818                        console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
36819                      }
36820                    }
36821
36822                    currentFrame.size = 0;
36823                  }
36824                }
36825
36826                currentFrame.data.push(packet);
36827                currentFrame.size += packet.byteLength;
36828              }
36829            }
36830
36831            break;
36832        }
36833
36834        if (endLoop && result.firstKeyFrame) {
36835          break;
36836        }
36837
36838        startIndex += MP2T_PACKET_LENGTH;
36839        endIndex += MP2T_PACKET_LENGTH;
36840        continue;
36841      } // If we get here, we have somehow become de-synchronized and we need to step
36842      // forward one byte at a time until we find a pair of sync bytes that denote
36843      // a packet
36844
36845
36846      startIndex++;
36847      endIndex++;
36848    } // Start walking from end of segment to get last video packet
36849
36850
36851    endIndex = bytes.byteLength;
36852    startIndex = endIndex - MP2T_PACKET_LENGTH;
36853    endLoop = false;
36854
36855    while (startIndex >= 0) {
36856      // Look for a pair of start and end sync bytes in the data..
36857      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36858        // We found a packet
36859        packet = bytes.subarray(startIndex, endIndex);
36860        type = probe$1.ts.parseType(packet, pmt.pid);
36861
36862        switch (type) {
36863          case 'pes':
36864            pesType = probe$1.ts.parsePesType(packet, pmt.table);
36865            pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet);
36866
36867            if (pesType === 'video' && pusi) {
36868              parsed = probe$1.ts.parsePesTime(packet);
36869
36870              if (parsed) {
36871                parsed.type = 'video';
36872                result.video.push(parsed);
36873                endLoop = true;
36874              }
36875            }
36876
36877            break;
36878        }
36879
36880        if (endLoop) {
36881          break;
36882        }
36883
36884        startIndex -= MP2T_PACKET_LENGTH;
36885        endIndex -= MP2T_PACKET_LENGTH;
36886        continue;
36887      } // If we get here, we have somehow become de-synchronized and we need to step
36888      // forward one byte at a time until we find a pair of sync bytes that denote
36889      // a packet
36890
36891
36892      startIndex--;
36893      endIndex--;
36894    }
36895  };
36896  /**
36897   * Adjusts the timestamp information for the segment to account for
36898   * rollover and convert to seconds based on pes packet timescale (90khz clock)
36899   */
36900
36901
36902  var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
36903    if (segmentInfo.audio && segmentInfo.audio.length) {
36904      var audioBaseTimestamp = baseTimestamp;
36905
36906      if (typeof audioBaseTimestamp === 'undefined') {
36907        audioBaseTimestamp = segmentInfo.audio[0].dts;
36908      }
36909
36910      segmentInfo.audio.forEach(function (info) {
36911        info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
36912        info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
36913
36914        info.dtsTime = info.dts / ONE_SECOND_IN_TS$1;
36915        info.ptsTime = info.pts / ONE_SECOND_IN_TS$1;
36916      });
36917    }
36918
36919    if (segmentInfo.video && segmentInfo.video.length) {
36920      var videoBaseTimestamp = baseTimestamp;
36921
36922      if (typeof videoBaseTimestamp === 'undefined') {
36923        videoBaseTimestamp = segmentInfo.video[0].dts;
36924      }
36925
36926      segmentInfo.video.forEach(function (info) {
36927        info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
36928        info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
36929
36930        info.dtsTime = info.dts / ONE_SECOND_IN_TS$1;
36931        info.ptsTime = info.pts / ONE_SECOND_IN_TS$1;
36932      });
36933
36934      if (segmentInfo.firstKeyFrame) {
36935        var frame = segmentInfo.firstKeyFrame;
36936        frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
36937        frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
36938
36939        frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1;
36940        frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1;
36941      }
36942    }
36943  };
36944  /**
36945   * inspects the aac data stream for start and end time information
36946   */
36947
36948
36949  var inspectAac_ = function inspectAac_(bytes) {
36950    var endLoop = false,
36951        audioCount = 0,
36952        sampleRate = null,
36953        timestamp = null,
36954        frameSize = 0,
36955        byteIndex = 0,
36956        packet;
36957
36958    while (bytes.length - byteIndex >= 3) {
36959      var type = probe$1.aac.parseType(bytes, byteIndex);
36960
36961      switch (type) {
36962        case 'timed-metadata':
36963          // Exit early because we don't have enough to parse
36964          // the ID3 tag header
36965          if (bytes.length - byteIndex < 10) {
36966            endLoop = true;
36967            break;
36968          }
36969
36970          frameSize = probe$1.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
36971          // to emit a full packet
36972
36973          if (frameSize > bytes.length) {
36974            endLoop = true;
36975            break;
36976          }
36977
36978          if (timestamp === null) {
36979            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
36980            timestamp = probe$1.aac.parseAacTimestamp(packet);
36981          }
36982
36983          byteIndex += frameSize;
36984          break;
36985
36986        case 'audio':
36987          // Exit early because we don't have enough to parse
36988          // the ADTS frame header
36989          if (bytes.length - byteIndex < 7) {
36990            endLoop = true;
36991            break;
36992          }
36993
36994          frameSize = probe$1.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
36995          // to emit a full packet
36996
36997          if (frameSize > bytes.length) {
36998            endLoop = true;
36999            break;
37000          }
37001
37002          if (sampleRate === null) {
37003            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
37004            sampleRate = probe$1.aac.parseSampleRate(packet);
37005          }
37006
37007          audioCount++;
37008          byteIndex += frameSize;
37009          break;
37010
37011        default:
37012          byteIndex++;
37013          break;
37014      }
37015
37016      if (endLoop) {
37017        return null;
37018      }
37019    }
37020
37021    if (sampleRate === null || timestamp === null) {
37022      return null;
37023    }
37024
37025    var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate;
37026    var result = {
37027      audio: [{
37028        type: 'audio',
37029        dts: timestamp,
37030        pts: timestamp
37031      }, {
37032        type: 'audio',
37033        dts: timestamp + audioCount * 1024 * audioTimescale,
37034        pts: timestamp + audioCount * 1024 * audioTimescale
37035      }]
37036    };
37037    return result;
37038  };
37039  /**
37040   * inspects the transport stream segment data for start and end time information
37041   * of the audio and video tracks (when present) as well as the first key frame's
37042   * start time.
37043   */
37044
37045
37046  var inspectTs_ = function inspectTs_(bytes) {
37047    var pmt = {
37048      pid: null,
37049      table: null
37050    };
37051    var result = {};
37052    parsePsi_(bytes, pmt);
37053
37054    for (var pid in pmt.table) {
37055      if (pmt.table.hasOwnProperty(pid)) {
37056        var type = pmt.table[pid];
37057
37058        switch (type) {
37059          case streamTypes.H264_STREAM_TYPE:
37060            result.video = [];
37061            parseVideoPes_(bytes, pmt, result);
37062
37063            if (result.video.length === 0) {
37064              delete result.video;
37065            }
37066
37067            break;
37068
37069          case streamTypes.ADTS_STREAM_TYPE:
37070            result.audio = [];
37071            parseAudioPes_(bytes, pmt, result);
37072
37073            if (result.audio.length === 0) {
37074              delete result.audio;
37075            }
37076
37077            break;
37078        }
37079      }
37080    }
37081
37082    return result;
37083  };
37084  /**
37085   * Inspects segment byte data and returns an object with start and end timing information
37086   *
37087   * @param {Uint8Array} bytes The segment byte data
37088   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
37089   *  timestamps for rollover. This value must be in 90khz clock.
37090   * @return {Object} Object containing start and end frame timing info of segment.
37091   */
37092
37093
37094  var inspect = function inspect(bytes, baseTimestamp) {
37095    var isAacData = probe$1.aac.isLikelyAacData(bytes);
37096    var result;
37097
37098    if (isAacData) {
37099      result = inspectAac_(bytes);
37100    } else {
37101      result = inspectTs_(bytes);
37102    }
37103
37104    if (!result || !result.audio && !result.video) {
37105      return null;
37106    }
37107
37108    adjustTimestamp_(result, baseTimestamp);
37109    return result;
37110  };
37111
37112  var tsInspector = {
37113    inspect: inspect,
37114    parseAudioPes_: parseAudioPes_
37115  };
37116
37117  /**
37118   * mux.js
37119   *
37120   * Copyright (c) Brightcove
37121   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
37122   */
37123  var toUnsigned = function toUnsigned(value) {
37124    return value >>> 0;
37125  };
37126
37127  var toHexString = function toHexString(value) {
37128    return ('00' + value.toString(16)).slice(-2);
37129  };
37130
37131  var bin = {
37132    toUnsigned: toUnsigned,
37133    toHexString: toHexString
37134  };
37135
37136  var parseType$2 = function parseType(buffer) {
37137    var result = '';
37138    result += String.fromCharCode(buffer[0]);
37139    result += String.fromCharCode(buffer[1]);
37140    result += String.fromCharCode(buffer[2]);
37141    result += String.fromCharCode(buffer[3]);
37142    return result;
37143  };
37144
37145  var parseType_1 = parseType$2;
37146
37147  var toUnsigned$1 = bin.toUnsigned;
37148
37149  var findBox = function findBox(data, path) {
37150    var results = [],
37151        i,
37152        size,
37153        type,
37154        end,
37155        subresults;
37156
37157    if (!path.length) {
37158      // short-circuit the search for empty paths
37159      return null;
37160    }
37161
37162    for (i = 0; i < data.byteLength;) {
37163      size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
37164      type = parseType_1(data.subarray(i + 4, i + 8));
37165      end = size > 1 ? i + size : data.byteLength;
37166
37167      if (type === path[0]) {
37168        if (path.length === 1) {
37169          // this is the end of the path and we've found the box we were
37170          // looking for
37171          results.push(data.subarray(i + 8, end));
37172        } else {
37173          // recursively search for the next box along the path
37174          subresults = findBox(data.subarray(i + 8, end), path.slice(1));
37175
37176          if (subresults.length) {
37177            results = results.concat(subresults);
37178          }
37179        }
37180      }
37181
37182      i = end;
37183    } // we've finished searching all of data
37184
37185
37186    return results;
37187  };
37188
37189  var findBox_1 = findBox;
37190
37191  var tfhd = function tfhd(data) {
37192    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37193        result = {
37194      version: data[0],
37195      flags: new Uint8Array(data.subarray(1, 4)),
37196      trackId: view.getUint32(4)
37197    },
37198        baseDataOffsetPresent = result.flags[2] & 0x01,
37199        sampleDescriptionIndexPresent = result.flags[2] & 0x02,
37200        defaultSampleDurationPresent = result.flags[2] & 0x08,
37201        defaultSampleSizePresent = result.flags[2] & 0x10,
37202        defaultSampleFlagsPresent = result.flags[2] & 0x20,
37203        durationIsEmpty = result.flags[0] & 0x010000,
37204        defaultBaseIsMoof = result.flags[0] & 0x020000,
37205        i;
37206    i = 8;
37207
37208    if (baseDataOffsetPresent) {
37209      i += 4; // truncate top 4 bytes
37210      // FIXME: should we read the full 64 bits?
37211
37212      result.baseDataOffset = view.getUint32(12);
37213      i += 4;
37214    }
37215
37216    if (sampleDescriptionIndexPresent) {
37217      result.sampleDescriptionIndex = view.getUint32(i);
37218      i += 4;
37219    }
37220
37221    if (defaultSampleDurationPresent) {
37222      result.defaultSampleDuration = view.getUint32(i);
37223      i += 4;
37224    }
37225
37226    if (defaultSampleSizePresent) {
37227      result.defaultSampleSize = view.getUint32(i);
37228      i += 4;
37229    }
37230
37231    if (defaultSampleFlagsPresent) {
37232      result.defaultSampleFlags = view.getUint32(i);
37233    }
37234
37235    if (durationIsEmpty) {
37236      result.durationIsEmpty = true;
37237    }
37238
37239    if (!baseDataOffsetPresent && defaultBaseIsMoof) {
37240      result.baseDataOffsetIsMoof = true;
37241    }
37242
37243    return result;
37244  };
37245
37246  var parseTfhd = tfhd;
37247
37248  var parseSampleFlags = function parseSampleFlags(flags) {
37249    return {
37250      isLeading: (flags[0] & 0x0c) >>> 2,
37251      dependsOn: flags[0] & 0x03,
37252      isDependedOn: (flags[1] & 0xc0) >>> 6,
37253      hasRedundancy: (flags[1] & 0x30) >>> 4,
37254      paddingValue: (flags[1] & 0x0e) >>> 1,
37255      isNonSyncSample: flags[1] & 0x01,
37256      degradationPriority: flags[2] << 8 | flags[3]
37257    };
37258  };
37259
37260  var parseSampleFlags_1 = parseSampleFlags;
37261
37262  var trun = function trun(data) {
37263    var result = {
37264      version: data[0],
37265      flags: new Uint8Array(data.subarray(1, 4)),
37266      samples: []
37267    },
37268        view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37269        // Flag interpretation
37270    dataOffsetPresent = result.flags[2] & 0x01,
37271        // compare with 2nd byte of 0x1
37272    firstSampleFlagsPresent = result.flags[2] & 0x04,
37273        // compare with 2nd byte of 0x4
37274    sampleDurationPresent = result.flags[1] & 0x01,
37275        // compare with 2nd byte of 0x100
37276    sampleSizePresent = result.flags[1] & 0x02,
37277        // compare with 2nd byte of 0x200
37278    sampleFlagsPresent = result.flags[1] & 0x04,
37279        // compare with 2nd byte of 0x400
37280    sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
37281        // compare with 2nd byte of 0x800
37282    sampleCount = view.getUint32(4),
37283        offset = 8,
37284        sample;
37285
37286    if (dataOffsetPresent) {
37287      // 32 bit signed integer
37288      result.dataOffset = view.getInt32(offset);
37289      offset += 4;
37290    } // Overrides the flags for the first sample only. The order of
37291    // optional values will be: duration, size, compositionTimeOffset
37292
37293
37294    if (firstSampleFlagsPresent && sampleCount) {
37295      sample = {
37296        flags: parseSampleFlags_1(data.subarray(offset, offset + 4))
37297      };
37298      offset += 4;
37299
37300      if (sampleDurationPresent) {
37301        sample.duration = view.getUint32(offset);
37302        offset += 4;
37303      }
37304
37305      if (sampleSizePresent) {
vendor: 8,367 bytes, lines 37306-37558
37306        sample.size = view.getUint32(offset);
37307        offset += 4;
37308      }
37309
37310      if (sampleCompositionTimeOffsetPresent) {
37311        if (result.version === 1) {
37312          sample.compositionTimeOffset = view.getInt32(offset);
37313        } else {
37314          sample.compositionTimeOffset = view.getUint32(offset);
37315        }
37316
37317        offset += 4;
37318      }
37319
37320      result.samples.push(sample);
37321      sampleCount--;
37322    }
37323
37324    while (sampleCount--) {
37325      sample = {};
37326
37327      if (sampleDurationPresent) {
37328        sample.duration = view.getUint32(offset);
37329        offset += 4;
37330      }
37331
37332      if (sampleSizePresent) {
37333        sample.size = view.getUint32(offset);
37334        offset += 4;
37335      }
37336
37337      if (sampleFlagsPresent) {
37338        sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4));
37339        offset += 4;
37340      }
37341
37342      if (sampleCompositionTimeOffsetPresent) {
37343        if (result.version === 1) {
37344          sample.compositionTimeOffset = view.getInt32(offset);
37345        } else {
37346          sample.compositionTimeOffset = view.getUint32(offset);
37347        }
37348
37349        offset += 4;
37350      }
37351
37352      result.samples.push(sample);
37353    }
37354
37355    return result;
37356  };
37357
37358  var parseTrun = trun;
37359
37360  var toUnsigned$2 = bin.toUnsigned;
37361
37362  var tfdt = function tfdt(data) {
37363    var result = {
37364      version: data[0],
37365      flags: new Uint8Array(data.subarray(1, 4)),
37366      baseMediaDecodeTime: toUnsigned$2(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
37367    };
37368
37369    if (result.version === 1) {
37370      result.baseMediaDecodeTime *= Math.pow(2, 32);
37371      result.baseMediaDecodeTime += toUnsigned$2(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
37372    }
37373
37374    return result;
37375  };
37376
37377  var parseTfdt = tfdt;
37378
37379  var toUnsigned$3 = bin.toUnsigned;
37380  var toHexString$1 = bin.toHexString;
37381  var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader;
37382  /**
37383   * Parses an MP4 initialization segment and extracts the timescale
37384   * values for any declared tracks. Timescale values indicate the
37385   * number of clock ticks per second to assume for time-based values
37386   * elsewhere in the MP4.
37387   *
37388   * To determine the start time of an MP4, you need two pieces of
37389   * information: the timescale unit and the earliest base media decode
37390   * time. Multiple timescales can be specified within an MP4 but the
37391   * base media decode time is always expressed in the timescale from
37392   * the media header box for the track:
37393   * ```
37394   * moov > trak > mdia > mdhd.timescale
37395   * ```
37396   * @param init {Uint8Array} the bytes of the init segment
37397   * @return {object} a hash of track ids to timescale values or null if
37398   * the init segment is malformed.
37399   */
37400
37401  timescale = function timescale(init) {
37402    var result = {},
37403        traks = findBox_1(init, ['moov', 'trak']); // mdhd timescale
37404
37405    return traks.reduce(function (result, trak) {
37406      var tkhd, version, index, id, mdhd;
37407      tkhd = findBox_1(trak, ['tkhd'])[0];
37408
37409      if (!tkhd) {
37410        return null;
37411      }
37412
37413      version = tkhd[0];
37414      index = version === 0 ? 12 : 20;
37415      id = toUnsigned$3(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
37416      mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
37417
37418      if (!mdhd) {
37419        return null;
37420      }
37421
37422      version = mdhd[0];
37423      index = version === 0 ? 12 : 20;
37424      result[id] = toUnsigned$3(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
37425      return result;
37426    }, result);
37427  };
37428  /**
37429   * Determine the base media decode start time, in seconds, for an MP4
37430   * fragment. If multiple fragments are specified, the earliest time is
37431   * returned.
37432   *
37433   * The base media decode time can be parsed from track fragment
37434   * metadata:
37435   * ```
37436   * moof > traf > tfdt.baseMediaDecodeTime
37437   * ```
37438   * It requires the timescale value from the mdhd to interpret.
37439   *
37440   * @param timescale {object} a hash of track ids to timescale values.
37441   * @return {number} the earliest base media decode start time for the
37442   * fragment, in seconds
37443   */
37444
37445
37446  startTime = function startTime(timescale, fragment) {
37447    var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
37448
37449    trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track
37450
37451    baseTimes = [].concat.apply([], trafs.map(function (traf) {
37452      return findBox_1(traf, ['tfhd']).map(function (tfhd) {
37453        var id, scale, baseTime; // get the track id from the tfhd
37454
37455        id = toUnsigned$3(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
37456
37457        scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
37458
37459        baseTime = findBox_1(traf, ['tfdt']).map(function (tfdt) {
37460          var version, result;
37461          version = tfdt[0];
37462          result = toUnsigned$3(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
37463
37464          if (version === 1) {
37465            result *= Math.pow(2, 32);
37466            result += toUnsigned$3(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
37467          }
37468
37469          return result;
37470        })[0];
37471        baseTime = baseTime || Infinity; // convert base time to seconds
37472
37473        return baseTime / scale;
37474      });
37475    })); // return the minimum
37476
37477    result = Math.min.apply(null, baseTimes);
37478    return isFinite(result) ? result : 0;
37479  };
37480  /**
37481   * Determine the composition start, in seconds, for an MP4
37482   * fragment.
37483   *
37484   * The composition start time of a fragment can be calculated using the base
37485   * media decode time, composition time offset, and timescale, as follows:
37486   *
37487   * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
37488   *
37489   * All of the aforementioned information is contained within a media fragment's
37490   * `traf` box, except for timescale info, which comes from the initialization
37491   * segment, so a track id (also contained within a `traf`) is also necessary to
37492   * associate it with a timescale
37493   *
37494   *
37495   * @param timescales {object} - a hash of track ids to timescale values.
37496   * @param fragment {Unit8Array} - the bytes of a media segment
37497   * @return {number} the composition start time for the fragment, in seconds
37498   **/
37499
37500
37501  compositionStartTime = function compositionStartTime(timescales, fragment) {
37502    var trafBoxes = findBox_1(fragment, ['moof', 'traf']);
37503    var baseMediaDecodeTime = 0;
37504    var compositionTimeOffset = 0;
37505    var trackId;
37506
37507    if (trafBoxes && trafBoxes.length) {
37508      // The spec states that track run samples contained within a `traf` box are contiguous, but
37509      // it does not explicitly state whether the `traf` boxes themselves are contiguous.
37510      // We will assume that they are, so we only need the first to calculate start time.
37511      var tfhd = findBox_1(trafBoxes[0], ['tfhd'])[0];
37512      var trun = findBox_1(trafBoxes[0], ['trun'])[0];
37513      var tfdt = findBox_1(trafBoxes[0], ['tfdt'])[0];
37514
37515      if (tfhd) {
37516        var parsedTfhd = parseTfhd(tfhd);
37517        trackId = parsedTfhd.trackId;
37518      }
37519
37520      if (tfdt) {
37521        var parsedTfdt = parseTfdt(tfdt);
37522        baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
37523      }
37524
37525      if (trun) {
37526        var parsedTrun = parseTrun(trun);
37527
37528        if (parsedTrun.samples && parsedTrun.samples.length) {
37529          compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
37530        }
37531      }
37532    } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
37533    // specified.
37534
37535
37536    var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
37537
37538    return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
37539  };
37540  /**
37541    * Find the trackIds of the video tracks in this source.
37542    * Found by parsing the Handler Reference and Track Header Boxes:
37543    *   moov > trak > mdia > hdlr
37544    *   moov > trak > tkhd
37545    *
37546    * @param {Uint8Array} init - The bytes of the init segment for this source
37547    * @return {Number[]} A list of trackIds
37548    *
37549    * @see ISO-BMFF-12/2015, Section 8.4.3
37550   **/
37551
37552
37553  getVideoTrackIds = function getVideoTrackIds(init) {
37554    var traks = findBox_1(init, ['moov', 'trak']);
37555    var videoTrackIds = [];
37556    traks.forEach(function (trak) {
37557      var hdlrs = findBox_1(trak, ['mdia', 'hdlr']);
37558      var tkhds = findBox_1(trak, ['tkhd']);
vendor: 4,496 bytes, lines 37559-37689
37559      hdlrs.forEach(function (hdlr, index) {
37560        var handlerType = parseType_1(hdlr.subarray(8, 12));
37561        var tkhd = tkhds[index];
37562        var view;
37563        var version;
37564        var trackId;
37565
37566        if (handlerType === 'vide') {
37567          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
37568          version = view.getUint8(0);
37569          trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
37570          videoTrackIds.push(trackId);
37571        }
37572      });
37573    });
37574    return videoTrackIds;
37575  };
37576
37577  getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) {
37578    // mdhd is a FullBox, meaning it will have its own version as the first byte
37579    var version = mdhd[0];
37580    var index = version === 0 ? 12 : 20;
37581    return toUnsigned$3(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
37582  };
37583  /**
37584   * Get all the video, audio, and hint tracks from a non fragmented
37585   * mp4 segment
37586   */
37587
37588
37589  getTracks = function getTracks(init) {
37590    var traks = findBox_1(init, ['moov', 'trak']);
37591    var tracks = [];
37592    traks.forEach(function (trak) {
37593      var track = {};
37594      var tkhd = findBox_1(trak, ['tkhd'])[0];
37595      var view, tkhdVersion; // id
37596
37597      if (tkhd) {
37598        view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
37599        tkhdVersion = view.getUint8(0);
37600        track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
37601      }
37602
37603      var hdlr = findBox_1(trak, ['mdia', 'hdlr'])[0]; // type
37604
37605      if (hdlr) {
37606        var type = parseType_1(hdlr.subarray(8, 12));
37607
37608        if (type === 'vide') {
37609          track.type = 'video';
37610        } else if (type === 'soun') {
37611          track.type = 'audio';
37612        } else {
37613          track.type = type;
37614        }
37615      } // codec
37616
37617
37618      var stsd = findBox_1(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
37619
37620      if (stsd) {
37621        var sampleDescriptions = stsd.subarray(8); // gives the codec type string
37622
37623        track.codec = parseType_1(sampleDescriptions.subarray(4, 8));
37624        var codecBox = findBox_1(sampleDescriptions, [track.codec])[0];
37625        var codecConfig, codecConfigType;
37626
37627        if (codecBox) {
37628          // https://tools.ietf.org/html/rfc6381#section-3.3
37629          if (/^[a-z]vc[1-9]$/i.test(track.codec)) {
37630            // we don't need anything but the "config" parameter of the
37631            // avc1 codecBox
37632            codecConfig = codecBox.subarray(78);
37633            codecConfigType = parseType_1(codecConfig.subarray(4, 8));
37634
37635            if (codecConfigType === 'avcC' && codecConfig.length > 11) {
37636              track.codec += '.'; // left padded with zeroes for single digit hex
37637              // profile idc
37638
37639              track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags
37640
37641              track.codec += toHexString$1(codecConfig[10]); // level idc
37642
37643              track.codec += toHexString$1(codecConfig[11]);
37644            } else {
37645              // TODO: show a warning that we couldn't parse the codec
37646              // and are using the default
37647              track.codec = 'avc1.4d400d';
37648            }
37649          } else if (/^mp4[a,v]$/i.test(track.codec)) {
37650            // we do not need anything but the streamDescriptor of the mp4a codecBox
37651            codecConfig = codecBox.subarray(28);
37652            codecConfigType = parseType_1(codecConfig.subarray(4, 8));
37653
37654            if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
37655              track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit
37656
37657              track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
37658            } else {
37659              // TODO: show a warning that we couldn't parse the codec
37660              // and are using the default
37661              track.codec = 'mp4a.40.2';
37662            }
37663          }
37664        }
37665      }
37666
37667      var mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
37668
37669      if (mdhd) {
37670        track.timescale = getTimescaleFromMediaHeader(mdhd);
37671      }
37672
37673      tracks.push(track);
37674    });
37675    return tracks;
37676  };
37677
37678  var probe$2 = {
37679    // export mp4 inspector's findBox and parseType for backwards compatibility
37680    findBox: findBox_1,
37681    parseType: parseType_1,
37682    timescale: timescale,
37683    startTime: startTime,
37684    compositionStartTime: compositionStartTime,
37685    videoTrackIds: getVideoTrackIds,
37686    tracks: getTracks,
37687    getTimescaleFromMediaHeader: getTimescaleFromMediaHeader
37688  };
37689
37690  var codecs = createCommonjsModule(function (module, exports) {
37691
37692    Object.defineProperty(exports, '__esModule', {
37693      value: true
37694    });
37695
37696    function _interopDefaultLegacy(e) {
37697      return e && typeof e === 'object' && 'default' in e ? e : {
37698        'default': e
37699      };
37700    }
37701
37702    var window__default = /*#__PURE__*/_interopDefaultLegacy(window$1);
37703
37704    var regexs = {
37705      // to determine mime types
37706      mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v)/,
37707      webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
37708      ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
37709      // to determine if a codec is audio or video
37710      video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
37711      audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3)/,
37712      // mux.js support regex
37713      muxerVideo: /^(avc0?1)/,
37714      muxerAudio: /^(mp4a)/
37715    };
37716    /**
37717     * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
37718     * `avc1.<hhhhhh>`
37719     *
37720     * @param {string} codec
37721     *        Codec string to translate
37722     * @return {string}
37723     *         The translated codec string
37724     */
37725
37726    var translateLegacyCodec = function translateLegacyCodec(codec) {
37727      if (!codec) {
37728        return codec;
37729      }
37730
37731      return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
37732        var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
37733        var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
37734        return 'avc1.' + profileHex + '00' + avcLevelHex;
37735      });
37736    };
37737    /**
37738     * Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
37739     * `avc1.<hhhhhh>`
37740     *
37741     * @param {string[]} codecs
37742     *        An array of codec strings to translate
37743     * @return {string[]}
37744     *         The translated array of codec strings
37745     */
37746
37747
37748    var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
37749      return codecs.map(translateLegacyCodec);
37750    };
37751    /**
37752     * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
37753     * standard `avc1.<hhhhhh>`.
37754     *
37755     * @param {string} codecString
37756     *        The codec string
37757     * @return {string}
37758     *         The codec string with old apple-style codecs replaced
37759     *
37760     * @private
37761     */
37762
37763
37764    var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
37765      return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
37766        return translateLegacyCodecs([match])[0];
37767      });
37768    };
37769    /**
37770     * @typedef {Object} ParsedCodecInfo
37771     * @property {number} codecCount
37772     *           Number of codecs parsed
37773     * @property {string} [videoCodec]
37774     *           Parsed video codec (if found)
37775     * @property {string} [videoObjectTypeIndicator]
37776     *           Video object type indicator (if found)
37777     * @property {string|null} audioProfile
37778     *           Audio profile
37779     */
37780
37781    /**
37782     * Parses a codec string to retrieve the number of codecs specified, the video codec and
37783     * object type indicator, and the audio profile.
37784     *
37785     * @param {string} [codecString]
37786     *        The codec string to parse
37787     * @return {ParsedCodecInfo}
37788     *         Parsed codec info
37789     */
37790
37791
37792    var parseCodecs = function parseCodecs(codecString) {
37793      if (codecString === void 0) {
37794        codecString = '';
37795      }
37796
37797      var codecs = codecString.split(',');
37798      var result = {};
37799      codecs.forEach(function (codec) {
37800        codec = codec.trim();
37801        ['video', 'audio'].forEach(function (name) {
37802          var match = regexs[name].exec(codec.toLowerCase());
37803
37804          if (!match || match.length <= 1) {
37805            return;
37806          } // maintain codec case
37807
37808
37809          var type = codec.substring(0, match[1].length);
37810          var details = codec.replace(type, '');
37811          result[name] = {
37812            type: type,
37813            details: details
37814          };
37815        });
37816      });
37817      return result;
37818    };
37819    /**
37820     * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
37821     * a default alternate audio playlist for the provided audio group.
37822     *
37823     * @param {Object} master
37824     *        The master playlist
37825     * @param {string} audioGroupId
37826     *        ID of the audio group for which to find the default co
37826dec info
37827     * @return {ParsedCodecInfo}
37828     *         Parsed codec info
37829     */
37830
37831
37832    var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
37833      if (!master.mediaGroups.AUDIO || !audioGroupId) {
37834        return null;
37835      }
37836
37837      var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
37838
37839      if (!audioGroup) {
37840        return null;
37841      }
37842
37843      for (var name in audioGroup) {
37844        var audioType = audioGroup[name];
37845
37846        if (audioType["default"] && audioType.playlists) {
37847          // codec should be the same for all playlists within the audio type
37848          return parseCodecs(audioType.playlists[0].attributes.CODECS);
37849        }
37850      }
37851
37852      return null;
37853    };
37854
37855    var isVideoCodec = function isVideoCodec(codec) {
37856      if (codec === void 0) {
37857        codec = '';
37858      }
37859
37860      return regexs.video.test(codec.trim().toLowerCase());
37861    };
37862
37863    var isAudioCodec = function isAudioCodec(codec) {
37864      if (codec === void 0) {
37865        codec = '';
37866      }
37867
37868      return regexs.audio.test(codec.trim().toLowerCase());
37869    };
37870
37871    var getMimeForCodec = function getMimeForCodec(codecString) {
37872      if (!codecString || typeof codecString !== 'string') {
37873        return;
37874      }
37875
37876      var codecs = codecString.toLowerCase().split(',').map(function (c) {
37877        return translateLegacyCodec(c.trim());
37878      }); // default to video type
37879
37880      var type = 'video'; // only change to audio type if the only codec we have is
37881      // audio
37882
37883      if (codecs.length === 1 && isAudioCodec(codecs[0])) {
37884        type = 'audio';
37885      } // default the container to mp4
37886
37887
37888      var container = 'mp4'; // every codec must be able to go into the container
37889      // for that container to be the correct one
37890
37891      if (codecs.every(function (c) {
37892        return regexs.mp4.test(c);
37893      })) {
37894        container = 'mp4';
37895      } else if (codecs.every(function (c) {
37896        return regexs.webm.test(c);
37897      })) {
37898        container = 'webm';
37899      } else if (codecs.every(function (c) {
37900        return regexs.ogg.test(c);
37901      })) {
37902        container = 'ogg';
37903      }
37904
37905      return type + "/" + container + ";codecs=\"" + codecString + "\"";
37906    };
37907
37908    var browserSupportsCodec = function browserSupportsCodec(codecString) {
37909      if (codecString === void 0) {
37910        codecString = '';
37911      }
37912
37913      return window__default['default'].MediaSource && window__default['default'].MediaSource.isTypeSupported && window__default['default'].MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
37914    };
37915
37916    var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
37917      if (codecString === void 0) {
37918        codecString = '';
37919      }
37920
37921      return codecString.toLowerCase().split(',').every(function (codec) {
37922        codec = codec.trim();
37923        return regexs.muxerVideo.test(codec) || regexs.muxerAudio.test(codec);
37924      });
37925    };
37926
37927    var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
37928    var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
37929    exports.DEFAULT_AUDIO_CODEC = DEFAULT_AUDIO_CODEC;
37930    exports.DEFAULT_VIDEO_CODEC = DEFAULT_VIDEO_CODEC;
37931    exports.browserSupportsCodec = browserSupportsCodec;
37932    exports.codecsFromDefault = codecsFromDefault;
37933    exports.getMimeForCodec = getMimeForCodec;
37934    exports.isAudioCodec = isAudioCodec;
37935    exports.isVideoCodec = isVideoCodec;
37936    exports.mapLegacyAvcCodecs = mapLegacyAvcCodecs;
37937    exports.muxerSupportsCodec = muxerSupportsCodec;
37938    exports.parseCodecs = parseCodecs;
37939    exports.translateLegacyCodec = translateLegacyCodec;
37940    exports.translateLegacyCodecs = translateLegacyCodecs;
37941  });
37942  unwrapExports(codecs);
37943  var codecs_1 = codecs.DEFAULT_AUDIO_CODEC;
37944  var codecs_2 = codecs.DEFAULT_VIDEO_CODEC;
37945  var codecs_3 = codecs.browserSupportsCodec;
37946  var codecs_4 = codecs.codecsFromDefault;
37947  var codecs_5 = codecs.getMimeForCodec;
37948  var codecs_6 = codecs.isAudioCodec;
37949  var codecs_7 = codecs.isVideoCodec;
37950  var codecs_8 = codecs.mapLegacyAvcCodecs;
37951  var codecs_9 = codecs.muxerSupportsCodec;
37952  var codecs_10 = codecs.parseCodecs;
37953  var codecs_11 = codecs.translateLegacyCodec;
37954  var codecs_12 = codecs.translateLegacyCodecs;
37955
37956  /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */
37957  /**
37958   * @file resolve-url.js - Handling how URLs are resolved and manipulated
37959   */
37960
37961  var resolveUrl$1 = resolveUrl_1;
37962  /**
37963   * Checks whether xhr request was redirected and returns correct url depending
37964   * on `handleManifestRedirects` option
37965   *
37966   * @api private
37967   *
37968   * @param  {string} url - an url being requested
37969   * @param  {XMLHttpRequest}
37969 req - xhr request result
37970   *
37971   * @return {string}
37972   */
37973
37974  var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
37975    // To understand how the responseURL below is set and generated:
37976    // - https://fetch.spec.whatwg.org/#concept-response-url
37977    // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
37978    if (handleManifestRedirect && req && req.responseURL && url !== req.responseURL) {
37979      return req.responseURL;
37980    }
37981
37982    return url;
37983  };
37984
37985  var log$1 = videojs$1.log;
37986
37987  var createPlaylistID = function createPlaylistID(index, uri) {
37988    return index + "-" + uri;
37989  };
37990  /**
37991   * Parses a given m3u8 playlist
37992   *
37993   * @param {string} manifestString
37994   *        The downloaded manifest string
37995   * @param {Object[]} [customTagParsers]
37996   *        An array of custom tag parsers for the m3u8-parser instance
37997   * @param {Object[]} [customTagMappers]
37998   *         An array of custom tag mappers for the m3u8-parser instance
37999   * @return {Object}
38000   *         The manifest object
38001   */
38002
38003
38004  var parseManifest = function parseManifest(_ref) {
38005    var manifestString = _ref.manifestString,
38006        _ref$customTagParsers = _ref.customTagParsers,
38007        customTagParsers = _ref$customTagParsers === void 0 ? [] : _ref$customTagParsers,
38008        _ref$customTagMappers = _ref.customTagMappers,
38009        customTagMappers = _ref$customTagMappers === void 0 ? [] : _ref$customTagMappers;
38010    var parser = new Parser();
38011    customTagParsers.forEach(function (customParser) {
38012      return parser.addParser(customParser);
38013    });
38014    customTagMappers.forEach(function (mapper) {
38015      return parser.addTagMapper(mapper);
38016    });
38017    parser.push(manifestString);
38018    parser.end();
38019    return parser.manifest;
38020  };
38021  /**
38022   * Loops through all supported media groups in master and calls the provided
38023   * callback for each group
38024   *
38025   * @param {Object} master
38026   *        The parsed master manifest object
38027   * @param {Function} callback
38028   *        Callback to call for each media group
38029   */
38030
38031
38032  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
38033    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
38034      for (var groupKey in master.mediaGroups[mediaType]) {
38035        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
38036          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
38037          callback(mediaProperties, mediaType, groupKey, labelKey);
38038        }
38039      }
38040    });
38041  };
38042  /**
38043   * Adds properties and attributes to the playlist to keep consistent functionality for
38044   * playlists throughout VHS.
38045   *
38046   * @param {Object} config
38047   *        Arguments object
38048   * @param {Object} config.playlist
38049   *        The media playlist
38050   * @param {string} [config.uri]
38051   *        The uri to the media playlist (if media playlist is not from within a master
38052   *        playlist)
38053   * @param {string} id
38054   *        ID to use for the playlist
38055   */
38056
38057
38058  var setupMediaPlaylist = function setupMediaPlaylist(_ref2) {
38059    var playlist = _ref2.playlist,
38060        uri = _ref2.uri,
38061        id = _ref2.id;
38062    playlist.id = id;
38063
38064    if (uri) {
38065      // For media playlists, m3u8-parser does not have access to a URI, as HLS media
38066      // playlists do not contain their own source URI, but one is needed for c
38066onsistency in
38067      // VHS.
38068      playlist.uri = uri;
38069    } // For HLS master playlists, even though certain attributes MUST be defined, the
38070    // stream may still be played without them.
38071    // For HLS media playlists, m3u8-parser does not attach an attributes object to the
38072    // manifest.
38073    //
38074    // To avoid undefined reference errors through the project, and make the code easier
38075    // to write/read, add an empty attributes object for these cases.
38076
38077
38078    playlist.attributes = playlist.attributes || {};
38079  };
38080  /**
38081   * Adds ID, resolvedUri, and attributes properties to each playlist of the master, where
38082   * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
38083   * playlist references to the playlists array.
38084   *
38085   * @param {Object} master
38086   *        The master playlist
38087   */
38088
38089
38090  var setupMediaPlaylists = function setupMediaPlaylists(master) {
38091    var i = master.playlists.length;
38092
38093    while (i--) {
38094      var playlist = master.playlists[i];
38095      setupMediaPlaylist({
38096        playlist: playlist,
38097        id: createPlaylistID(i, playlist.uri)
38098      });
38099      playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
38100      master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
38101
38102      master.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
38103      // the stream can be played without it. Although an attributes property may have been
38104      // added to the playlist to prevent undefined references, issue a warning to fix the
38105      // manifest.
38106
38107      if (!playlist.attributes.BANDWIDTH) {
38108        log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
38109      }
38110    }
38111  };
38112  /**
38113   * Adds resolvedUri properties to each media group.
38114   *
38115   * @param {Object} master
38116   *        The master playlist
38117   */
38118
38119
38120  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
38121    forEachMediaGroup(master, function (properties) {
38122      if (properties.uri) {
38123        properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
38124      }
38125    });
38126  };
38127  /**
38128   * Creates a master playlist wrapper to insert a sole media playlist into.
38129   *
38130   * @param {Object} media
38131   *        Media playlist
38132   * @param {string} uri
38133   *        The media URI
38134   *
38135   * @return {Object}
38136   *         Master playlist
38137   */
38138
38139
38140  var masterForMedia = function masterForMedia(media, uri) {
38141    var id = createPlaylistID(0, uri);
38142    var master = {
38143      mediaGroups: {
38144        'AUDIO': {},
38145        'VIDEO': {},
38146        'CLOSED-CAPTIONS': {},
38147        'SUBTITLES': {}
38148      },
38149      uri: window$1.location.href,
38150      resolvedUri: window$1.location.href,
38151      playlists: [{
38152        uri: uri,
38153        id: id,
38154        resolvedUri: uri,
38155        // m3u8-parser does not attach an attributes property to media playlists so make
38156        // sure that the property is attached to avoid undefined reference errors
38157        attributes: {}
38158      }]
38159    }; // set up ID reference
38160
38161    master.playlists[id] = master.playlists[0]; // URI reference added for backwards compatibility
38162
38163    master.playlists[uri] = master.playlists[0];
38164    return master;
38165  };
38166  /**
38167   * Does an in-place update of the master manifest to add updated playlist URI references
38168   * as well as other properties needed by VHS that aren't included by the parser.
38169   *
38170   * @param {Object} master
38171   *        Master manifest object
38172   * @param {string} uri
38173   *        The source URI
38174   */
38175
38176
38177  var addPropertiesToMaster = function addPropertiesToMaster(master, uri) {
38178    master.uri = uri;
38179
38180    for (var i = 0; i < master.playlists.length; i++) {
38181      if (!master.playlists[i].uri) {
38182        // Set up phony URIs for the playlists since playlists are referenced by their URIs
38183        // throughout VHS, but some formats (e.g., DASH) don't have external URIs
38184        // TODO: consider adding dummy URIs in mpd-parser
38185        var phonyUri = "placeholder-uri-" + i;
38186        master.playlists[i].uri = phonyUri;
38187      }
38188    }
38189
38190    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
38191      if (!properties.playlists || !properties.playlists.length || properties.playlists[0].uri) {
38192        return;
38193      } // Set up phony URIs for the media group playlists since playlists are referenced by
38194      // their URIs throughout VHS, but some formats (e.g., DASH) don't have external URIs
38195
38196
38197      var phonyUri = "placeholder-uri-" + mediaType + "-" + groupKey + "-" + labelKey;
38198      var id = createPlaylistID(0, phonyUri);
38199      properties.playlists[0].uri = phonyUri;
38200      properties.playlists[0].id = id; // setup ID and URI references (URI for backwards compatibility)
38201
38202      master.playlists[id] = properties.playlists[0];
38203      master.playlists[phonyUri] = properties.playlists[0];
38204    });
38205    setupMediaPlaylists(master);
38206    resolveMediaGroupUris(master);
38207  };
38208
38209  var mergeOptions$1 = videojs$1.mergeOptions,
38210      EventTarget$1 = videojs$1.EventTarget;
38211  /**
38212    * Returns a new array of segments that is the result of merging
38213    * properties from an older list of segments onto an updated
38214    * list. No properties on the updated playlist will be overridden.
38215    *
38216    * @param {Array} original the outdated list of segments
38217    * @param {Array} update the updated list of segments
38218    * @param {number=} offset the index of the first update
38219    * segment in the original segment list. For non-live playlists,
38220    * this should always be zero and does not need to be
38221    * specified. For live playlists, it should be the difference
38222    * between the media sequence numbers in the original and updated
38223    * playlists.
38224    * @return a list of merged segment objects
38225    */
38226
38227  var updateSegments = function updateSegments(original, update, offset) {
38228    var result = update.slice();
38229    offset = offset || 0;
38230    var length = Math.min(original.length, update.length + offset);
38231
38232    for (var i = offset; i < length; i++) {
38233      result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
38234    }
38235
38236    return result;
38237  };
38238
38239  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
38240    if (!segment.resolvedUri) {
38241      segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
38242    }
38243
38244    if (segment.key && !segment.key.resolvedUri) {
38245      segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
38246    }
38247
38248    if (segment.map && !segment.map.resolvedUri) {
38249      segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
38250    }
38251  };
38252  /**
38253    * Returns a new master playlist that is the result of merging an
38254    * updated media playlist into the original version. If the
38255    * updated media playlist does not match any of the playlist
38256    * entries in the original master playlist, null is returned.
38257    *
38258    * @param {Object} master a parsed master M3U8 object
38259    * @param {Object} media a parsed media M3U8 object
38260    * @return {Object} a new object that represents the original
38261    * master playlist with the updated media playlist merged in, or
38262    * null if the merge produced no change.
38263    */
38264
38265
38266  var updateMaster = function updateMaster(master, media) {
38267    var result = mergeOptions$1(master, {});
38268    var playlist = result.playlists[media.id];
38269
38270    if (!playlist) {
38271      return null;
38272    } // consider the playlist unchanged if the number of segments is equal, the media
38273    // sequence number is unchanged, and this playlist hasn't become the end of the playlist
38274
38275
38276    if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
38277      return null;
38278    }
38279
38280    var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
38281
38282    if (playlist.segments) {
38283      mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
38284    } // resolve any segment URIs to prevent us from having to do it later
38285
38286
38287    mergedPlaylist.segments.forEach(function (segment) {
38288      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
38289    }); // TODO Right now in the playlists array there are two references to each playlist, one
38290    // that is referenced by index, and one by URI. The index reference may no longer be
38291    // necessary.
38292
38293    for (var i = 0; i < result.playlists.length; i++) {
38294      if (result.playlists[i].id === media.id) {
38295        result.playlists[i] = mergedPlaylist;
38296      }
38297    }
38298
38299    result.playlists[media.id] = mergedPlaylist; // URI reference added for backwards compatibility
38300
38301    result.playlists[media.uri] = mergedPlaylist;
38302    return result;
38303  };
38304  /**
38305   * Calculates the time to wait before refreshing a live playlist
38306   *
38307   * @param {Object} media
38308   *        The current media
38309   * @param {boolean} update
38310   *        True if there were any updates from the last refresh, false otherwise
38311   * @return {number}
38312   *         The time in ms to wait before refreshing the live playlist
38313   */
38314
38315
38316  var refreshDelay = function refreshDelay(media, update) {
38317    var lastSegment = media.segments[media.segments.length - 1];
38318    var delay;
38319
38320    if (update && lastSegment && lastSegment.duration) {
38321      delay = lastSegment.duration * 1000;
38322    } else {
38323      // if the playlist is unchanged since the last reload or last segment duration
38324      // cannot be determined, try again after half the target duration
38325      delay = (media.targetDuration || 10) * 500;
38326    }
38327
38328    return delay;
38329  };
38330  /**
38331   * Load a playlist from a remote location
38332   *
38333   * @class PlaylistLoader
38334   * @extends Stream
38335   * @param {string|Object} src url or object of manifest
38336   * @param {boolean} withCredentials the withCredentials xhr option
38337   * @class
38338   */
38339
38340
38341  var PlaylistLoader = /*#__PURE__*/function (_EventTarget) {
38342    inheritsLoose(PlaylistLoader, _EventTarget);
38343
38344    function PlaylistLoader(src, vhs, options) {
38345      var _this;
38346
38347      if (options === void 0) {
38348        options = {};
38349      }
38350
38351      _this = _EventTarget.call(this) || this;
38352
38353      if (!src) {
38354        throw new Error('A non-empty playlist URL or object is required');
38355      }
38356
38357      var _options = options,
38358          _options$withCredenti = _options.withCredentials,
38359          withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
38360          _options$handleManife = _options.handleManifestRedirects,
38361          handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
38362      _this.src = src;
38363      _this.vhs_ = vhs;
38364      _this.withCredentials = withCredentials;
38365      _this.handleManifestRedirects = handleManifestRedirects;
38366      var vhsOptions = vhs.options_;
38367      _this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
38368      _this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || []; // initialize the loader state
38369
38370      _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
38371
38372      _this.on('mediaupdatetimeout', function () {
38373        if (_this.state !== 'HAVE_METADATA') {
38374          // only refresh the media playlist if no other activity is going on
38375          return;
38376        }
38377
38378        _this.state = 'HAVE_CURRENT_METADATA';
38379        _this.request = _this.vhs_.xhr({
38380          uri: resolveUrl$1(_this.master.uri, _this.media().uri),
38381          withCredentials: _this.withCredentials
38382        }, function (error, req) {
38383          // disposed
38384          if (!_this.request) {
38385            return;
38386          }
38387
38388          if (error) {
38389            return _this.playlistRequestError(_this.request, _this.media(), 'HAVE_METADATA');
38390          }
38391
38392          _this.haveMetadata({
38393            playlistString: _this.request.responseText,
38394            url: _this.media().uri,
38395            id: _this.media().id
38396          });
38397        });
38398      });
38399
38400      return _this;
38401    }
38402
38403    var _proto = PlaylistLoader.prototype;
38404
38405    _proto.playlistRequestError = function playlistRequestError(xhr, playlist, startingState) {
38406      var uri = playlist.uri,
38407          id = playlist.id; // any in-flight request is now finished
38408
38409      this.request = null;
38410
38411      if (startingState) {
38412        this.state = startingState;
38413      }
38414
38415      this.error = {
38416        playlist: this.master.playlists[id],
38417        status: xhr.status,
38418        message: "HLS playlist request error at URL: " + uri + ".",
38419        responseText: xhr.responseText,
38420        code: xhr.status >= 500 ? 4 : 2
38421      };
38422      this.trigger('error');
38423    }
38424    /**
38425     * Update the playlist loader's state in response to a new or updated playlist.
38426     *
38427     * @param {string} [playlistString]
vendor: 23,003 bytes, lines 38428-39204
38428     *        Playlist string (if playlistObject is not provided)
38429     * @param {Object} [playlistObject]
38430     *        Playlist object (if playlistString is not provided)
38431     * @param {string} url
38432     *        URL of playlist
38433     * @param {string} id
38434     *        ID to use for playlist
38435     */
38436    ;
38437
38438    _proto.haveMetadata = function haveMetadata(_ref) {
38439      var _this2 = this;
38440
38441      var playlistString = _ref.playlistString,
38442          playlistObject = _ref.playlistObject,
38443          url = _ref.url,
38444          id = _ref.id; // any in-flight request is now finished
38445
38446      this.request = null;
38447      this.state = 'HAVE_METADATA';
38448      var playlist = playlistObject || parseManifest({
38449        manifestString: playlistString,
38450        customTagParsers: this.customTagParsers,
38451        customTagMappers: this.customTagMappers
38452      });
38453      setupMediaPlaylist({
38454        playlist: playlist,
38455        uri: url,
38456        id: id
38457      }); // merge this playlist into the master
38458
38459      var update = updateMaster(this.master, playlist);
38460      this.targetDuration = playlist.targetDuration;
38461
38462      if (update) {
38463        this.master = update;
38464        this.media_ = this.master.playlists[id];
38465      } else {
38466        this.trigger('playlistunchanged');
38467      } // refresh live playlists after a target duration passes
38468
38469
38470      if (!this.media().endList) {
38471        window$1.clearTimeout(this.mediaUpdateTimeout);
38472        this.mediaUpdateTimeout = window$1.setTimeout(function () {
38473          _this2.trigger('mediaupdatetimeout');
38474        }, refreshDelay(this.media(), !!update));
38475      }
38476
38477      this.trigger('loadedplaylist');
38478    }
38479    /**
38480      * Abort any outstanding work and clean up.
38481      */
38482    ;
38483
38484    _proto.dispose = function dispose() {
38485      this.trigger('dispose');
38486      this.stopRequest();
38487      window$1.clearTimeout(this.mediaUpdateTimeout);
38488      window$1.clearTimeout(this.finalRenditionTimeout);
38489      this.off();
38490    };
38491
38492    _proto.stopRequest = function stopRequest() {
38493      if (this.request) {
38494        var oldRequest = this.request;
38495        this.request = null;
38496        oldRequest.onreadystatechange = null;
38497        oldRequest.abort();
38498      }
38499    }
38500    /**
38501      * When called without any arguments, returns the currently
38502      * active media playlist. When called with a single argument,
38503      * triggers the playlist loader to asynchronously switch to the
38504      * specified media playlist. Calling this method while the
38505      * loader is in the HAVE_NOTHING causes an error to be emitted
38506      * but otherwise has no effect.
38507      *
38508      * @param {Object=} playlist the parsed media playlist
38509      * object to switch to
38510      * @param {boolean=} is this the last available playlist
38511      *
38512      * @return {Playlist} the current loaded media
38513      */
38514    ;
38515
38516    _proto.media = function media(playlist, isFinalRendition) {
38517      var _this3 = this; // getter
38518
38519
38520      if (!playlist) {
38521        return this.media_;
38522      } // setter
38523
38524
38525      if (this.state === 'HAVE_NOTHING') {
38526        throw new Error('Cannot switch media playlist from ' + this.state);
38527      } // find the playlist object if the target playlist has been
38528      // specified by URI
38529
38530
38531      if (typeof playlist === 'string') {
38532        if (!this.master.playlists[playlist]) {
38533          throw new Error('Unknown playlist URI: ' + playlist);
38534        }
38535
38536        playlist = this.master.playlists[playlist];
38537      }
38538
38539      window$1.clearTimeout(this.finalRenditionTimeout);
38540
38541      if (isFinalRendition) {
38542        var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000;
38543        this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay);
38544        return;
38545      }
38546
38547      var startingState = this.state;
38548      var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to fully loaded playlists immediately
38549
38550      if (this.master.playlists[playlist.id].endList || // handle the case of a playlist object (e.g., if using vhs-json with a resolved
38551      // media playlist or, for the case of demuxed audio, a resolved audio media group)
38552      playlist.endList && playlist.segments.length) {
38553        // abort outstanding playlist requests
38554        if (this.request) {
38555          this.request.onreadystatechange = null;
38556          this.request.abort();
38557          this.request = null;
38558        }
38559
38560        this.state = 'HAVE_METADATA';
38561        this.media_ = playlist; // trigger media change if the active media has been updated
38562
38563        if (mediaChange) {
38564          this.trigger('mediachanging');
38565
38566          if (startingState === 'HAVE_MASTER') {
38567            // The initial playlist was a master manifest, and the first media selected was
38568            // also provided (in the form of a resolved playlist object) as part of the
38569            // source object (rather than just a URL). Therefore, since the media playlist
38570            // doesn't need to be requested, loadedmetadata won't trigger as part of the
38571            // normal flow, and needs an explicit trigger here.
38572            this.trigger('loadedmetadata');
38573          } else {
38574            this.trigger('mediachange');
38575          }
38576        }
38577
38578        return;
38579      } // switching to the active playlist is a no-op
38580
38581
38582      if (!mediaChange) {
38583        return;
38584      }
38585
38586      this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
38587
38588      if (this.request) {
38589        if (playlist.resolvedUri === this.request.url) {
38590          // requesting to switch to the same playlist multiple times
38591          // has no effect after the first
38592          return;
38593        }
38594
38595        this.request.onreadystatechange = null;
38596        this.request.abort();
38597        this.request = null;
38598      } // request the new playlist
38599
38600
38601      if (this.media_) {
38602        this.trigger('mediachanging');
38603      }
38604
38605      this.request = this.vhs_.xhr({
38606        uri: playlist.resolvedUri,
38607        withCredentials: this.withCredentials
38608      }, function (error, req) {
38609        // disposed
38610        if (!_this3.request) {
38611          return;
38612        }
38613
38614        playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
38615
38616        if (error) {
38617          return _this3.playlistRequestError(_this3.request, playlist, startingState);
38618        }
38619
38620        _this3.haveMetadata({
38621          playlistString: req.responseText,
38622          url: playlist.uri,
38623          id: playlist.id
38624        }); // fire loadedmetadata the first time a media playlist is loaded
38625
38626
38627        if (startingState === 'HAVE_MASTER') {
38628          _this3.trigger('loadedmetadata');
38629        } else {
38630          _this3.trigger('mediachange');
38631        }
38632      });
38633    }
38634    /**
38635     * pause loading of the playlist
38636     */
38637    ;
38638
38639    _proto.pause = function pause() {
38640      this.stopRequest();
38641      window$1.clearTimeout(this.mediaUpdateTimeout);
38642
38643      if (this.state === 'HAVE_NOTHING') {
38644        // If we pause the loader before any data has been retrieved, its as if we never
38645        // started, so reset to an unstarted state.
38646        this.started = false;
38647      } // Need to restore state now that no activity is happening
38648
38649
38650      if (this.state === 'SWITCHING_MEDIA') {
38651        // if the loader was in the process of switching media, it should either return to
38652        // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
38653        // playlist yet. This is determined by the existence of loader.media_
38654        if (this.media_) {
38655          this.state = 'HAVE_METADATA';
38656        } else {
38657          this.state = 'HAVE_MASTER';
38658        }
38659      } else if (this.state === 'HAVE_CURRENT_METADATA') {
38660        this.state = 'HAVE_METADATA';
38661      }
38662    }
38663    /**
38664     * start loading of the playlist
38665     */
38666    ;
38667
38668    _proto.load = function load(isFinalRendition) {
38669      var _this4 = this;
38670
38671      window$1.clearTimeout(this.mediaUpdateTimeout);
38672      var media = this.media();
38673
38674      if (isFinalRendition) {
38675        var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
38676        this.mediaUpdateTimeout = window$1.setTimeout(function () {
38677          return _this4.load();
38678        }, delay);
38679        return;
38680      }
38681
38682      if (!this.started) {
38683        this.start();
38684        return;
38685      }
38686
38687      if (media && !media.endList) {
38688        this.trigger('mediaupdatetimeout');
38689      } else {
38690        this.trigger('loadedplaylist');
38691      }
38692    }
38693    /**
38694     * start loading of the playlist
38695     */
38696    ;
38697
38698    _proto.start = function start() {
38699      var _this5 = this;
38700
38701      this.started = true;
38702
38703      if (typeof this.src === 'object') {
38704        // in the case of an entirely constructed manifest object (meaning there's no actual
38705        // manifest on a server), default the uri to the page's href
38706        if (!this.src.uri) {
38707          this.src.uri = window$1.location.href;
38708        } // resolvedUri is added on internally after the initial request. Since there's no
38709        // request for pre-resolved manifests, add on resolvedUri here.
38710
38711
38712        this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
38713        // request can be skipped (since the top level of the manifest, at a minimum, is
38714        // already available as a parsed manifest object). However, if the manifest object
38715        // represents a master playlist, some media playlists may need to be resolved before
38716        // the starting segment list is available. Therefore, go directly to setup of the
38717        // initial playlist, and let the normal flow continue from there.
38718        //
38719        // Note that the call to setup is asynchronous, as other sections of VHS may assume
38720        // that the first request is asynchronous.
38721
38722        setTimeout(function () {
38723          _this5.setupInitialPlaylist(_this5.src);
38724        }, 0);
38725        return;
38726      } // request the specified URL
38727
38728
38729      this.request = this.vhs_.xhr({
38730        uri: this.src,
38731        withCredentials: this.withCredentials
38732      }, function (error, req) {
38733        // disposed
38734        if (!_this5.request) {
38735          return;
38736        } // clear the loader's request reference
38737
38738
38739        _this5.request = null;
38740
38741        if (error) {
38742          _this5.error = {
38743            status: req.status,
38744            message: "HLS playlist request error at URL: " + _this5.src + ".",
38745            responseText: req.responseText,
38746            // MEDIA_ERR_NETWORK
38747            code: 2
38748          };
38749
38750          if (_this5.state === 'HAVE_NOTHING') {
38751            _this5.started = false;
38752          }
38753
38754          return _this5.trigger('error');
38755        }
38756
38757        _this5.src = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.src, req);
38758        var manifest = parseManifest({
38759          manifestString: req.responseText,
38760          customTagParsers: _this5.customTagParsers,
38761          customTagMappers: _this5.customTagMappers
38762        });
38763
38764        _this5.setupInitialPlaylist(manifest);
38765      });
38766    };
38767
38768    _proto.srcUri = function srcUri() {
38769      return typeof this.src === 'string' ? this.src : this.src.uri;
38770    }
38771    /**
38772     * Given a manifest object that's either a master or media playlist, trigger the proper
38773     * events and set the state of the playlist loader.
38774     *
38775     * If the manifest object represents a master playlist, `loadedplaylist` will be
38776     * triggered to allow listeners to select a playlist. If none is selected, the loader
38777     * will default to the first one in the playlists array.
38778     *
38779     * If the manifest object represents a media playlist, `loadedplaylist` will be
38780     * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
38781     *
38782     * In the case of a media playlist, a master playlist object wrapper with one playlist
38783     * will be created so that all logic can handle playlists in the same fashion (as an
38784     * assumed manifest object schema).
38785     *
38786     * @param {Object} manifest
38787     *        The parsed manifest object
38788     */
38789    ;
38790
38791    _proto.setupInitialPlaylist = function setupInitialPlaylist(manifest) {
38792      this.state = 'HAVE_MASTER';
38793
38794      if (manifest.playlists) {
38795        this.master = manifest;
38796        addPropertiesToMaster(this.master, this.srcUri()); // If the initial master playlist has playlists wtih segments already resolved,
38797        // then resolve URIs in advance, as they are usually done after a playlist request,
38798        // which may not happen if the playlist is resolved.
38799
38800        manifest.playlists.forEach(function (playlist) {
38801          if (playlist.segments) {
38802            playlist.segments.forEach(function (segment) {
38803              resolveSegmentUris(segment, playlist.resolvedUri);
38804            });
38805          }
38806        });
38807        this.trigger('loadedplaylist');
38808
38809        if (!this.request) {
38810          // no media playlist was specifically selected so start
38811          // from the first listed one
38812          this.media(this.master.playlists[0]);
38813        }
38814
38815        return;
38816      } // In order to support media playlists passed in as vhs-json, the case where the uri
38817      // is not provided as part of the manifest should be considered, and an appropriate
38818      // default used.
38819
38820
38821      var uri = this.srcUri() || window$1.location.href;
38822      this.master = masterForMedia(manifest, uri);
38823      this.haveMetadata({
38824        playlistObject: manifest,
38825        url: uri,
38826        id: this.master.playlists[0].id
38827      });
38828      this.trigger('loadedmetadata');
38829    };
38830
38831    return PlaylistLoader;
38832  }(EventTarget$1);
38833  /**
38834   * ranges
38835   *
38836   * Utilities for working with TimeRanges.
38837   *
38838   */
38839
38840
38841  var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
38842  // can be misleading because of precision differences or when the current media has poorly
38843  // aligned audio and video, which can cause values to be slightly off from what you would
38844  // expect. This value is what we consider to be safe to use in such comparisons to account
38845  // for these scenarios.
38846
38847  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
38848
38849  var filterRanges = function filterRanges(timeRanges, predicate) {
38850    var results = [];
38851    var i;
38852
38853    if (timeRanges && timeRanges.length) {
38854      // Search for ranges that match the predicate
38855      for (i = 0; i < timeRanges.length; i++) {
38856        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
38857          results.push([timeRanges.start(i), timeRanges.end(i)]);
38858        }
38859      }
38860    }
38861
38862    return videojs$1.createTimeRanges(results);
38863  };
38864  /**
38865   * Attempts to find the buffered TimeRange that contains the specified
38866   * time.
38867   *
38868   * @param {TimeRanges} buffered - the TimeRanges object to query
38869   * @param {number} time  - the time to filter on.
38870   * @return {TimeRanges} a new TimeRanges object
38871   */
38872
38873
38874  var findRange = function findRange(buffered, time) {
38875    return filterRanges(buffered, function (start, end) {
38876      return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
38877    });
38878  };
38879  /**
38880   * Returns the TimeRanges that begin later than the specified time.
38881   *
38882   * @param {TimeRanges} timeRanges - the TimeRanges object to query
38883   * @param {number} time - the time to filter on.
38884   * @return {TimeRanges} a new TimeRanges object.
38885   */
38886
38887
38888  var findNextRange = function findNextRange(timeRanges, time) {
38889    return filterRanges(timeRanges, function (start) {
38890      return start - TIME_FUDGE_FACTOR >= time;
38891    });
38892  };
38893  /**
38894   * Returns gaps within a list of TimeRanges
38895   *
38896   * @param {TimeRanges} buffered - the TimeRanges object
38897   * @return {TimeRanges} a TimeRanges object of gaps
38898   */
38899
38900
38901  var findGaps = function findGaps(buffered) {
38902    if (buffered.length < 2) {
38903      return videojs$1.createTimeRanges();
38904    }
38905
38906    var ranges = [];
38907
38908    for (var i = 1; i < buffered.length; i++) {
38909      var start = buffered.end(i - 1);
38910      var end = buffered.start(i);
38911      ranges.push([start, end]);
38912    }
38913
38914    return videojs$1.createTimeRanges(ranges);
38915  };
38916  /**
38917   * Calculate the intersection of two TimeRanges
38918   *
38919   * @param {TimeRanges} bufferA
38920   * @param {TimeRanges} bufferB
38921   * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
38922   */
38923
38924
38925  var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
38926    var start = null;
38927    var end = null;
38928    var arity = 0;
38929    var extents = [];
38930    var ranges = [];
38931
38932    if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
38933      return videojs$1.createTimeRange();
38934    } // Handle the case where we have both buffers and create an
38935    // intersection of the two
38936
38937
38938    var count = bufferA.length; // A) Gather up all start and end times
38939
38940    while (count--) {
38941      extents.push({
38942        time: bufferA.start(count),
38943        type: 'start'
38944      });
38945      extents.push({
38946        time: bufferA.end(count),
38947        type: 'end'
38948      });
38949    }
38950
38951    count = bufferB.length;
38952
38953    while (count--) {
38954      extents.push({
38955        time: bufferB.start(count),
38956        type: 'start'
38957      });
38958      extents.push({
38959        time: bufferB.end(count),
38960        type: 'end'
38961      });
38962    } // B) Sort them by time
38963
38964
38965    extents.sort(function (a, b) {
38966      return a.time - b.time;
38967    }); // C) Go along one by one incrementing arity for start and decrementing
38968    //    arity for ends
38969
38970    for (count = 0; count < extents.length; count++) {
38971      if (extents[count].type === 'start') {
38972        arity++; // D) If arity is ever incremented to 2 we are entering an
38973        //    overlapping range
38974
38975        if (arity === 2) {
38976          start = extents[count].time;
38977        }
38978      } else if (extents[count].type === 'end') {
38979        arity--; // E) If arity is ever decremented to 1 we leaving an
38980        //    overlapping range
38981
38982        if (arity === 1) {
38983          end = extents[count].time;
38984        }
38985      } // F) Record overlapping ranges
38986
38987
38988      if (start !== null && end !== null) {
38989        ranges.push([start, end]);
38990        start = null;
38991        end = null;
38992      }
38993    }
38994
38995    return videojs$1.createTimeRanges(ranges);
38996  };
38997  /**
38998   * Gets a human readable string for a TimeRange
38999   *
39000   * @param {TimeRange} range
39001   * @return {string} a human readable string
39002   */
39003
39004
39005  var printableRange = function printableRange(range) {
39006    var strArr = [];
39007
39008    if (!range || !range.length) {
39009      return '';
39010    }
39011
39012    for (var i = 0; i < range.length; i++) {
39013      strArr.push(range.start(i) + ' => ' + range.end(i));
39014    }
39015
39016    return strArr.join(', ');
39017  };
39018  /**
39019   * Calculates the amount of time left in seconds until the player hits the end of the
39020   * buffer and causes a rebuffer
39021   *
39022   * @param {TimeRange} buffered
39023   *        The state of the buffer
39024   * @param {Numnber} currentTime
39025   *        The current time of the player
39026   * @param {number} playbackRate
39027   *        The current playback rate of the player. Defaults to 1.
39028   * @return {number}
39029   *         Time until the player has to start rebuffering in seconds.
39030   * @function timeUntilRebuffer
39031   */
39032
39033
39034  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime, playbackRate) {
39035    if (playbackRate === void 0) {
39036      playbackRate = 1;
39037    }
39038
39039    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
39040    return (bufferedEnd - currentTime) / playbackRate;
39041  };
39042  /**
39043   * Converts a TimeRanges object into an array representation
39044   *
39045   * @param {TimeRanges} timeRanges
39046   * @return {Array}
39047   */
39048
39049
39050  var timeRangesToArray = function timeRangesToArray(timeRanges) {
39051    var timeRangesList = [];
39052
39053    for (var i = 0; i < timeRanges.length; i++) {
39054      timeRangesList.push({
39055        start: timeRanges.start(i),
39056        end: timeRanges.end(i)
39057      });
39058    }
39059
39060    return timeRangesList;
39061  };
39062  /**
39063   * Determines if two time range objects are different.
39064   *
39065   * @param {TimeRange} a
39066   *        the first time range object to check
39067   *
39068   * @param {TimeRange} b
39069   *        the second time range object to check
39070   *
39071   * @return {Boolean}
39072   *         Whether the time range objects differ
39073   */
39074
39075
39076  var isRangeDifferent = function isRangeDifferent(a, b) {
39077    // same object
39078    if (a === b) {
39079      return false;
39080    } // one or the other is undefined
39081
39082
39083    if (!a && b || !b && a) {
39084      return true;
39085    } // length is different
39086
39087
39088    if (a.length !== b.length) {
39089      return true;
39090    } // see if any start/end pair is different
39091
39092
39093    for (var i = 0; i < a.length; i++) {
39094      if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
39095        return true;
39096      }
39097    } // if the length and every pair is the same
39098    // this is the same time range
39099
39100
39101    return false;
39102  };
39103  /**
39104   * @file playlist.js
39105   *
39106   * Playlist related utilities.
39107   */
39108
39109
39110  var createTimeRange = videojs$1.createTimeRange;
39111  /**
39112   * walk backward until we find a duration we can use
39113   * or return a failure
39114   *
39115   * @param {Playlist} playlist the playlist to walk through
39116   * @param {Number} endSequence the mediaSequence to stop walking on
39117   */
39118
39119  var backwardDuration = function backwardDuration(playlist, endSequence) {
39120    var result = 0;
39121    var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
39122    // the interval, use it
39123
39124    var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
39125    // information that is earlier than endSequence
39126
39127    if (segment) {
39128      if (typeof segment.start !== 'undefined') {
39129        return {
39130          result: segment.start,
39131          precise: true
39132        };
39133      }
39134
39135      if (typeof segment.end !== 'undefined') {
39136        return {
39137          result: segment.end - segment.duration,
39138          precise: true
39139        };
39140      }
39141    }
39142
39143    while (i--) {
39144      segment = playlist.segments[i];
39145
39146      if (typeof segment.end !== 'undefined') {
39147        return {
39148          result: result + segment.end,
39149          precise: true
39150        };
39151      }
39152
39153      result += segment.duration;
39154
39155      if (typeof segment.start !== 'undefined') {
39156        return {
39157          result: result + segment.start,
39158          precise: true
39159        };
39160      }
39161    }
39162
39163    return {
39164      result: result,
39165      precise: false
39166    };
39167  };
39168  /**
39169   * walk forward until we find a duration we can use
39170   * or return a failure
39171   *
39172   * @param {Playlist} playlist the playlist to walk through
39173   * @param {number} endSequence the mediaSequence to stop walking on
39174   */
39175
39176
39177  var forwardDuration = function forwardDuration(playlist, endSequence) {
39178    var result = 0;
39179    var segment;
39180    var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
39181    // information
39182
39183    for (; i < playlist.segments.length; i++) {
39184      segment = playlist.segments[i];
39185
39186      if (typeof segment.start !== 'undefined') {
39187        return {
39188          result: segment.start - result,
39189          precise: true
39190        };
39191      }
39192
39193      result += segment.duration;
39194
39195      if (typeof segment.end !== 'undefined') {
39196        return {
39197          result: segment.end - result,
39198          precise: true
39199        };
39200      }
39201    } // indicate we didn't find a useful duration estimate
39202
39203
39204    return {
vendor: 4,627 bytes, lines 39205-39345
39205      result: -1,
39206      precise: false
39207    };
39208  };
39209  /**
39210    * Calculate the media duration from the segments associated with a
39211    * playlist. The duration of a subinterval of the available segments
39212    * may be calculated by specifying an end index.
39213    *
39214    * @param {Object} playlist a media playlist object
39215    * @param {number=} endSequence an exclusive upper boundary
39216    * for the playlist.  Defaults to playlist length.
39217    * @param {number} expired the amount of time that has dropped
39218    * off the front of the playlist in a live scenario
39219    * @return {number} the duration between the first available segment
39220    * and end index.
39221    */
39222
39223
39224  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
39225    if (typeof endSequence === 'undefined') {
39226      endSequence = playlist.mediaSequence + playlist.segments.length;
39227    }
39228
39229    if (endSequence < playlist.mediaSequence) {
39230      return 0;
39231    } // do a backward walk to estimate the duration
39232
39233
39234    var backward = backwardDuration(playlist, endSequence);
39235
39236    if (backward.precise) {
39237      // if we were able to base our duration estimate on timing
39238      // information provided directly from the Media Source, return
39239      // it
39240      return backward.result;
39241    } // walk forward to see if a precise duration estimate can be made
39242    // that way
39243
39244
39245    var forward = forwardDuration(playlist, endSequence);
39246
39247    if (forward.precise) {
39248      // we found a segment that has been buffered and so it's
39249      // position is known precisely
39250      return forward.result;
39251    } // return the less-precise, playlist-based duration estimate
39252
39253
39254    return backward.result + expired;
39255  };
39256  /**
39257    * Calculates the duration of a playlist. If a start and end index
39258    * are specified, the duration will be for the subset of the media
39259    * timeline between those two indices. The total duration for live
39260    * playlists is always Infinity.
39261    *
39262    * @param {Object} playlist a media playlist object
39263    * @param {number=} endSequence an exclusive upper
39264    * boundary for the playlist. Defaults to the playlist media
39265    * sequence number plus its length.
39266    * @param {number=} expired the amount of time that has
39267    * dropped off the front of the playlist in a live scenario
39268    * @return {number} the duration between the start index and end
39269    * index.
39270    */
39271
39272
39273  var duration = function duration(playlist, endSequence, expired) {
39274    if (!playlist) {
39275      return 0;
39276    }
39277
39278    if (typeof expired !== 'number') {
39279      expired = 0;
39280    } // if a slice of the total duration is not requested, use
39281    // playlist-level duration indicators when they're present
39282
39283
39284    if (typeof endSequence === 'undefined') {
39285      // if present, use the duration specified in the playlist
39286      if (playlist.totalDuration) {
39287        return playlist.totalDuration;
39288      } // duration should be Infinity for live playlists
39289
39290
39291      if (!playlist.endList) {
39292        return window$1.Infinity;
39293      }
39294    } // calculate the total duration based on the segment durations
39295
39296
39297    return intervalDuration(playlist, endSequence, expired);
39298  };
39299  /**
39300    * Calculate the time between two indexes in the current playlist
39301    * neight the start- nor the end-index need to be within the current
39302    * playlist in which case, the targetDuration of the playlist is used
39303    * to approximate the durations of the segments
39304    *
39305    * @param {Object} playlist a media playlist object
39306    * @param {number} startIndex
39307    * @param {number} endIndex
39308    * @return {number} the number of seconds between startIndex and endIndex
39309    */
39310
39311
39312  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
39313    var durations = 0;
39314
39315    if (startIndex > endIndex) {
39316      var _ref = [endIndex, startIndex];
39317      startIndex = _ref[0];
39318      endIndex = _ref[1];
39319    }
39320
39321    if (startIndex < 0) {
39322      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
39323        durations += playlist.targetDuration;
39324      }
39325
39326      startIndex = 0;
39327    }
39328
39329    for (var _i = startIndex; _i < endIndex; _i++) {
39330      durations += playlist.segments[_i].duration;
39331    }
39332
39333    return durations;
39334  };
39335  /**
39336   * Determines the media index of the segment corresponding to the safe edge of the live
39337   * window which is the duration of the last segment plus 2 target durations from the end
39338   * of the playlist.
39339   *
39340   * A liveEdgePadding can be provided which will be used instead of calculating the safe live edge.
39341   * This corresponds to suggestedPresentationDelay in DASH manifests.
39342   *
39343   * @param {Object} playlist
39344   *        a media playlist object
39345   * @param {number} [liveEdgePadding]
39346   *        A number in seconds indicating how far from the end we want to be.
39347   *        If provided, this value is used instead of calculating the safe live index from the target durations.
39348   *        Corresponds to suggestedPresentationDelay in DASH manifests.
39349   * @return {number}
39350   *         The media index of the segment at the safe live point. 0 if there is no "safe"
39351   *         point.
39352   * @function safeLiveIndex
39353   */
39354
39355
39356  var safeLiveIndex = function safeLiveIndex(playlist, liveEdgePadding) {
39357    if (!playlist.segments.length) {
39358      return 0;
39359    }
39360
39361    var i = playlist.segments.length;
39362    var lastSegmentDuration = playlist.segments[i - 1].duration || playlist.targetDuration;
39363    var safeDistance = typeof liveEdgePadding === 'number' ? liveEdgePadding : lastSegmentDuration + playlist.targetDuration * 2;
39364
39365    if (safeDistance === 0) {
39366      return i;
39367    }
39368
39369    var distanceFromEnd = 0;
39370
39371    while (i--) {
39372      distanceFromEnd += playlist.segments[i].duration;
39373
39374      if (distanceFromEnd >= safeDistance) {
39375        break;
39376      }
39377    }
39378
39379    return Math.max(0, i);
39380  };
39381  /**
39382   * Calculates the playlist end time
39383   *
39384   * @param {Object} playlist a media playlist object
39385   * @param {number=} expired the amount of time that has
39386   *                  dropped off the front of the playlist in a live scenario
39387   * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
39388   *                        playlist end calculation should consider the safe live end
39389   *                        (truncate the playlist end by three segments). This is normally
39390   *                        used for calculating the end of the playlist's seekable range.
39391   *                        This takes into account the value of liveEdgePadding.
39392   *                        Setting liveEdgePadding to 0 is equivalent to setting this to false.
39393   * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
39394   *                 If this is provided, it is used in the safe live end calculation.
39395   *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
39396   *                 Corresponds to suggestedPresentationDelay in DASH manifests.
39397   * @return {number} the end time of playlist
39398   * @function playlistEnd
39399   */
39400
39401
39402  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) {
39403    if (!playlist || !playlist.segments) {
39404      return null;
39405    }
39406
39407    if (playlist.endList) {
39408      return duration(playlist);
39409    }
39410
39411    if (expired === null) {
39412      return null;
39413    }
39414
39415    expired = expired || 0;
39416    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist, liveEdgePadding) : playlist.segments.length;
39417    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
39418  };
39419  /**
39420    * Calculates the interval of time that is currently seekable in a
39421    * playlist. The returned time ranges are relative to the earliest
39422    * moment in the specified playlist that is still available. A full
39423    * seekable implementation for live streams would need to offset
39424    * these values by the duration of content that has expired from the
39425    * stream.
39426    *
39427    * @param {Object} playlist a media playlist object
39428    * dropped off the front of the playlist in a live scenario
39429    * @param {number=} expired the amount of time that has
39430    * dropped off the front of the playlist in a live scenario
39431    * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
39432    *        Corresponds to suggestedPresentationDelay in DASH manifests.
39433    * @return {TimeRanges} the periods of time that are valid targets
39434    * for seeking
39435    */
39436
39437
39438  var seekable = function seekable(playlist, expired, liveEdgePadding) {
39439    var useSafeLiveEnd = true;
39440    var seekableStart = expired || 0;
39441    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
39442
39443    if (seekableEnd === null) {
39444      return createTimeRange();
39445    }
39446
39447    return createTimeRange(seekableStart, seekableEnd);
39448  };
39449  /**
39450   * Determine the index and estimated starting time of the segment that
39451   * contains a specified playback position in a media playlist.
39452   *
39453   * @param {Object} playlist the media playlist to query
39454   * @param {number} currentTime The number of seconds since the earliest
39455   * possible position to determine the containing segment for
39456   * @param {number} startIndex
39457   * @param {number} startTime
39458   * @return {Object}
39459   */
39460
39461
39462  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
39463    var i;
39464    var segment;
39465    var numSegments = playlist.segments.length;
39466    var time = currentTime - startTime;
39467
39468    if (time < 0) {
39469      // Walk backward from startIndex in the playlist, adding durations
39470      // until we find a segment that contains `time` and return it
39471      if (startIndex > 0) {
39472        for (i = startIndex - 1; i >= 0; i--) {
39473          segment = playlist.segments[i];
39474          time += segment.duration + TIME_FUDGE_FACTOR;
39475
39476          if (time > 0) {
39477            return {
39478              mediaIndex: i,
39479              startTime: startTime - sumDurations(playlist, startIndex, i)
39480            };
39481          }
39482        }
39483      } // We were unable to find a good segment within the playlist
39484      // so select the first segment
39485
39486
39487      return {
39488        mediaIndex: 0,
39489        startTime: currentTime
39490      };
39491    } // When startIndex is negative, we first walk forward to first segment
39492    // adding target durations. If we "run out of time" before getting to
39493    // the first segment, return the first segment
39494
39495
39496    if (startIndex < 0) {
39497      for (i = startIndex; i < 0; i++) {
39498        time -= playlist.targetDuration;
39499
39500        if (time < 0) {
39501          return {
39502            mediaIndex: 0,
39503            startTime: currentTime
39504          };
39505        }
39506      }
39507
39508      startIndex = 0;
39509    } // Walk forward from startIndex in the playlist, subtracting durations
39510    // until we find a segment that contains `time` and return it
39511
39512
39513    for (i = startIndex; i < numSegments; i++) {
39514      segment = playlist.segments[i];
39515      time -= segment.duration + TIME_FUDGE_FACTOR;
39516
39517      if (time < 0) {
39518        return {
39519          mediaIndex: i,
39520          startTime: startTime + sumDurations(playlist, startIndex, i)
39521        };
39522      }
39523    } // We are out of possible candidates so load the last one...
39524
39525
39526    return {
39527      mediaIndex: numSegments - 1,
39528      startTime: currentTime
39529    };
39530  };
39531  /**
39532   * Check whether the playlist is blacklisted or not.
39533   *
39534   * @param {Object} playlist the media playlist object
39535   * @return {boolean} whether the playlist is blacklisted or not
39536   * @function isBlacklisted
39537   */
39538
39539
39540  var isBlacklisted = function isBlacklisted(playlist) {
39541    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
39542  };
39543  /**
39544   * Check whether the playlist is compatible with current playback configuration or has
39545   * been blacklisted permanently for being incompatible.
39546   *
39547   * @param {Object} playlist the media playlist object
39548   * @return {boolean} whether the playlist is incompatible or not
39549   * @function isIncompatible
39550   */
39551
39552
39553  var isIncompatible = function isIncompatible(playlist) {
39554    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
39555  };
39556  /**
39557   * Check whether the playlist is enabled or not.
39558   *
39559   * @param {Object} playlist the media playlist object
39560   * @return {boolean} whether the playlist is enabled or not
39561   * @function isEnabled
39562   */
39563
39564
39565  var isEnabled = function isEnabled(playlist) {
39566    var blacklisted = isBlacklisted(playlist);
39567    return !playlist.disabled && !blacklisted;
39568  };
39569  /**
39570   * Check whether the playlist has been manually disabled through the representations api.
39571   *
39572   * @param {Object} playlist the media playlist object
39573   * @return {boolean} whether the playlist is disabled manually or not
39574   * @function isDisabled
39575   */
39576
39577
39578  var isDisabled = function isDisabled(playlist) {
39579    return playlist.disabled;
39580  };
39581  /**
39582   * Returns whether the current playlist is an AES encrypted HLS stream
39583   *
39584   * @return {boolean} true if it's an AES encrypted HLS stream
39585   */
39586
39587
39588  var isAes = function isAes(media) {
39589    for (var i = 0; i < media.segments.length; i++) {
39590      if (media.segments[i].key) {
39591        return true;
39592      }
39593    }
39594
39595    return false;
vendor: 7,464 bytes, lines 39596-39838
39596  };
39597  /**
39598   * Checks if the playlist has a value for the specified attribute
39599   *
39600   * @param {string} attr
39601   *        Attribute to check for
39602   * @param {Object} playlist
39603   *        The media playlist object
39604   * @return {boolean}
39605   *         Whether the playlist contains a value for the attribute or not
39606   * @function hasAttribute
39607   */
39608
39609
39610  var hasAttribute = function hasAttribute(attr, playlist) {
39611    return playlist.attributes && playlist.attributes[attr];
39612  };
39613  /**
39614   * Estimates the time required to complete a segment download from the specified playlist
39615   *
39616   * @param {number} segmentDuration
39617   *        Duration of requested segment
39618   * @param {number} bandwidth
39619   *        Current measured bandwidth of the player
39620   * @param {Object} playlist
39621   *        The media playlist object
39622   * @param {number=} bytesReceived
39623   *        Number of bytes already received for the request. Defaults to 0
39624   * @return {number|NaN}
39625   *         The estimated time to request the segment. NaN if bandwidth information for
39626   *         the given playlist is unavailable
39627   * @function estimateSegmentRequestTime
39628   */
39629
39630
39631  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist, bytesReceived) {
39632    if (bytesReceived === void 0) {
39633      bytesReceived = 0;
39634    }
39635
39636    if (!hasAttribute('BANDWIDTH', playlist)) {
39637      return NaN;
39638    }
39639
39640    var size = segmentDuration * playlist.attributes.BANDWIDTH;
39641    return (size - bytesReceived * 8) / bandwidth;
39642  };
39643  /*
39644   * Returns whether the current playlist is the lowest rendition
39645   *
39646   * @return {Boolean} true if on lowest rendition
39647   */
39648
39649
39650  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
39651    if (master.playlists.length === 1) {
39652      return true;
39653    }
39654
39655    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
39656    return master.playlists.filter(function (playlist) {
39657      if (!isEnabled(playlist)) {
39658        return false;
39659      }
39660
39661      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
39662    }).length === 0;
39663  }; // exports
39664
39665
39666  var Playlist = {
39667    duration: duration,
39668    seekable: seekable,
39669    safeLiveIndex: safeLiveIndex,
39670    getMediaInfoForTime: getMediaInfoForTime,
39671    isEnabled: isEnabled,
39672    isDisabled: isDisabled,
39673    isBlacklisted: isBlacklisted,
39674    isIncompatible: isIncompatible,
39675    playlistEnd: playlistEnd,
39676    isAes: isAes,
39677    hasAttribute: hasAttribute,
39678    estimateSegmentRequestTime: estimateSegmentRequestTime,
39679    isLowestEnabledRendition: isLowestEnabledRendition
39680  };
39681  /**
39682   * @file xhr.js
39683   */
39684
39685  var videojsXHR = videojs$1.xhr,
39686      mergeOptions$1$1 = videojs$1.mergeOptions;
39687
39688  var callbackWrapper = function callbackWrapper(request, error, response, callback) {
39689    var reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
39690
39691    if (!error && reqResponse) {
39692      request.responseTime = Date.now();
39693      request.roundTripTime = request.responseTime - request.requestTime;
39694      request.bytesReceived = reqResponse.byteLength || reqResponse.length;
39695
39696      if (!request.bandwidth) {
39697        request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
39698      }
39699    }
39700
39701    if (response.headers) {
39702      request.responseHeaders = response.headers;
39703    } // videojs.xhr now uses a specific code on the error
39704    // object to signal that a request has timed out instead
39705    // of setting a boolean on the request object
39706
39707
39708    if (error && error.code === 'ETIMEDOUT') {
39709      request.timedout = true;
39710    } // videojs.xhr no longer considers status codes outside of 200 and 0
39711    // (for file uris) to be errors, but the old XHR did, so emulate that
39712    // behavior. Status 206 may be used in response to byterange requests.
39713
39714
39715    if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
39716      error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
39717    }
39718
39719    callback(error, request);
39720  };
39721
39722  var xhrFactory = function xhrFactory() {
39723    var xhr = function XhrFunction(options, callback) {
39724      // Add a default timeout
39725      options = mergeOptions$1$1({
39726        timeout: 45e3
39727      }, options); // Allow an optional user-specified function to modify the option
39728      // object before we construct the xhr request
39729
39730      var beforeRequest = XhrFunction.beforeRequest || videojs$1.Vhs.xhr.beforeRequest;
39731
39732      if (beforeRequest && typeof beforeRequest === 'function') {
39733        var newOptions = beforeRequest(options);
39734
39735        if (newOptions) {
39736          options = newOptions;
39737        }
39738      }
39739
39740      var request = videojsXHR(options, function (error, response) {
39741        return callbackWrapper(request, error, response, callback);
39742      });
39743      var originalAbort = request.abort;
39744
39745      request.abort = function () {
39746        request.aborted = true;
39747        return originalAbort.apply(request, arguments);
39748      };
39749
39750      request.uri = options.uri;
39751      request.requestTime = Date.now();
39752      return request;
39753    };
39754
39755    return xhr;
39756  };
39757  /**
39758   * Turns segment byterange into a string suitable for use in
39759   * HTTP Range requests
39760   *
39761   * @param {Object} byterange - an object with two values defining the start and end
39762   *                             of a byte-range
39763   */
39764
39765
39766  var byterangeStr = function byterangeStr(byterange) {
39767    // `byterangeEnd` is one less than `offset + length` because the HTTP range
39768    // header uses inclusive ranges
39769    var byterangeEnd = byterange.offset + byterange.length - 1;
39770    var byterangeStart = byterange.offset;
39771    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
39772  };
39773  /**
39774   * Defines headers for use in the xhr request for a particular segment.
39775   *
39776   * @param {Object} segment - a simplified copy of the segmentInfo object
39777   *                           from SegmentLoader
39778   */
39779
39780
39781  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
39782    var headers = {};
39783
39784    if (segment.byterange) {
39785      headers.Range = byterangeStr(segment.byterange);
39786    }
39787
39788    return headers;
39789  };
39790  /**
39791   * @file bin-utils.js
39792   */
39793
39794  /**
39795   * convert a TimeRange to text
39796   *
39797   * @param {TimeRange} range the timerange to use for conversion
39798   * @param {number} i the iterator on the range to convert
39799   */
39800
39801
39802  var textRange = function textRange(range, i) {
39803    return range.start(i) + '-' + range.end(i);
39804  };
39805  /**
39806   * format a number as hex string
39807   *
39808   * @param {number} e The number
39809   * @param {number} i the iterator
39810   */
39811
39812
39813  var formatHexString = function formatHexString(e, i) {
39814    var value = e.toString(16);
39815    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
39816  };
39817
39818  var formatAsciiString = function formatAsciiString(e) {
39819    if (e >= 0x20 && e < 0x7e) {
39820      return String.fromCharCode(e);
39821    }
39822
39823    return '.';
39824  };
39825  /**
39826   * Creates an object for sending to a web worker modifying properties that are TypedArrays
39827   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
39828   *
39829   * @param {Object} message
39830   *        Object of properties and values to send to the web worker
39831   * @return {Object}
39832   *         Modified message with TypedArray values expanded
39833   * @function createTransferableMessage
39834   */
39835
39836
39837  var createTransferableMessage = function createTransferableMessage(message) {
39838    var transferable = {};
vendor: 33,153 bytes, lines 39839-40813
39839    Object.keys(message).forEach(function (key) {
39840      var value = message[key];
39841
39842      if (ArrayBuffer.isView(value)) {
39843        transferable[key] = {
39844          bytes: value.buffer,
39845          byteOffset: value.byteOffset,
39846          byteLength: value.byteLength
39847        };
39848      } else {
39849        transferable[key] = value;
39850      }
39851    });
39852    return transferable;
39853  };
39854  /**
39855   * Returns a unique string identifier for a media initialization
39856   * segment.
39857   */
39858
39859
39860  var initSegmentId = function initSegmentId(initSegment) {
39861    var byterange = initSegment.byterange || {
39862      length: Infinity,
39863      offset: 0
39864    };
39865    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
39866  };
39867  /**
39868   * Returns a unique string identifier for a media segment key.
39869   */
39870
39871
39872  var segmentKeyId = function segmentKeyId(key) {
39873    return key.resolvedUri;
39874  };
39875  /**
39876   * utils to help dump binary data to the console
39877   */
39878
39879
39880  var hexDump = function hexDump(data) {
39881    var bytes = Array.prototype.slice.call(data);
39882    var step = 16;
39883    var result = '';
39884    var hex;
39885    var ascii;
39886
39887    for (var j = 0; j < bytes.length / step; j++) {
39888      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
39889      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
39890      result += hex + ' ' + ascii + '\n';
39891    }
39892
39893    return result;
39894  };
39895
39896  var tagDump = function tagDump(_ref) {
39897    var bytes = _ref.bytes;
39898    return hexDump(bytes);
39899  };
39900
39901  var textRanges = function textRanges(ranges) {
39902    var result = '';
39903    var i;
39904
39905    for (i = 0; i < ranges.length; i++) {
39906      result += textRange(ranges, i) + ' ';
39907    }
39908
39909    return result;
39910  };
39911
39912  var utils$1 = /*#__PURE__*/Object.freeze({
39913    __proto__: null,
39914    createTransferableMessage: createTransferableMessage,
39915    initSegmentId: initSegmentId,
39916    segmentKeyId: segmentKeyId,
39917    hexDump: hexDump,
39918    tagDump: tagDump,
39919    textRanges: textRanges
39920  }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
39921  // 25% was arbitrarily chosen, and may need to be refined over time.
39922
39923  var SEGMENT_END_FUDGE_PERCENT = 0.25;
39924  /**
39925   * Converts a player time (any time that can be gotten/set from player.currentTime(),
39926   * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
39927   * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
39928   *
39929   * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
39930   * point" (a point where we have a mapping from program time to player time, with player
39931   * time being the post transmux start of the segment).
39932   *
39933   * For more details, see [this doc](../../docs/program-time-from-player-time.md).
39934   *
39935   * @param {number} playerTime the player time
39936   * @param {Object} segment the segment which contains the player time
39937   * @return {Date} program time
39938   */
39939
39940  var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
39941    if (!segment.dateTimeObject) {
39942      // Can't convert without an "anchor point" for the program time (i.e., a time that can
39943      // be used to map the start of a segment with a real world time).
39944      return null;
39945    }
39946
39947    var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
39948    var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
39949
39950    var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
39951    var offsetFromSegmentStart = playerTime - startOfSegment;
39952    return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
39953  };
39954
39955  var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
39956    return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
39957  };
39958  /**
39959   * Finds a segment that contains the time requested given as an ISO-8601 string. The
39960   * returned segment might be an estimate or an accurate match.
39961   *
39962   * @param {string} programTime The ISO-8601 programTime to find a match for
39963   * @param {Object} playlist A playlist object to search within
39964   */
39965
39966
39967  var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
39968    // Assumptions:
39969    //  - verifyProgramDateTimeTags has already been run
39970    //  - live streams have been started
39971    var dateTimeObject;
39972
39973    try {
39974      dateTimeObject = new Date(programTime);
39975    } catch (e) {
39976      return null;
39977    }
39978
39979    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
39980      return null;
39981    }
39982
39983    var segment = playlist.segments[0];
39984
39985    if (dateTimeObject < segment.dateTimeObject) {
39986      // Requested time is before stream start.
39987      return null;
39988    }
39989
39990    for (var i = 0; i < playlist.segments.length - 1; i++) {
39991      segment = playlist.segments[i];
39992      var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
39993
39994      if (dateTimeObject < nextSegmentStart) {
39995        break;
39996      }
39997    }
39998
39999    var lastSegment = playlist.segments[playlist.segments.length - 1];
40000    var lastSegmentStart = lastSegment.dateTimeObject;
40001    var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
40002    var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
40003
40004    if (dateTimeObject > lastSegmentEnd) {
40005      // Beyond the end of the stream, or our best guess of the end of the stream.
40006      return null;
40007    }
40008
40009    if (dateTimeObject > lastSegmentStart) {
40010      segment = lastSegment;
40011    }
40012
40013    return {
40014      segment: segment,
40015      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
40016      // Although, given that all segments have accurate date time objects, the segment
40017      // selected should be accurate, unless the video has been transmuxed at some point
40018      // (determined by the presence of the videoTimingInfo object), the segment's "player
40019      // time" (the start time in the player) can't be considered accurate.
40020      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
40021    };
40022  };
40023  /**
40024   * Finds a segment that contains the given player time(in seconds).
40025   *
40026   * @param {number} time The player time to find a match for
40027   * @param {Object} playlist A playlist object to search within
40028   */
40029
40030
40031  var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
40032    // Assumptions:
40033    // - there will always be a segment.duration
40034    // - we can start from zero
40035    // - segments are in time order
40036    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
40037      return null;
40038    }
40039
40040    var segmentEnd = 0;
40041    var segment;
40042
40043    for (var i = 0; i < playlist.segments.length; i++) {
40044      segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
40045      // should contain the most accurate values we have for the segment's player times.
40046      //
40047      // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
40048      // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
40049      // calculate an end value.
40050
40051      segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
40052
40053      if (time <= segmentEnd) {
40054        break;
40055      }
40056    }
40057
40058    var lastSegment = playlist.segments[playlist.segments.length - 1];
40059
40060    if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
40061      // The time requested is beyond the stream end.
40062      return null;
40063    }
40064
40065    if (time > segmentEnd) {
40066      // The time is within or beyond the last segment.
40067      //
40068      // Check to see if the time is beyond a reasonable guess of the end of the stream.
40069      if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
40070        // Technically, because the duration value is only an estimate, the time may still
40071        // exist in the last segment, however, there isn't enough information to make even
40072        // a reasonable estimate.
40073        return null;
40074      }
40075
40076      segment = lastSegment;
40077    }
40078
40079    return {
40080      segment: segment,
40081      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
40082      // Because videoTimingInfo is only set after transmux, it is the only way to get
40083      // accurate timing values.
40084      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
40085    };
40086  };
40087  /**
40088   * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
40089   * If the offset returned is positive, the programTime occurs after the
40090   * comparisonTimestamp.
40091   * If the offset is negative, the programTime occurs before the comparisonTimestamp.
40092   *
40093   * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
40094   * @param {string} programTime The programTime as an ISO-8601 string
40095   * @return {number} offset
40096   */
40097
40098
40099  var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
40100    var segmentDateTime;
40101    var programDateTime;
40102
40103    try {
40104      segmentDateTime = new Date(comparisonTimeStamp);
40105      programDateTime = new Date(programTime);
40106    } catch (e) {// TODO handle error
40107    }
40108
40109    var segmentTimeEpoch = segmentDateTime.getTime();
40110    var programTimeEpoch = programDateTime.getTime();
40111    return (programTimeEpoch - segmentTimeEpoch) / 1000;
40112  };
40113  /**
40114   * Checks that all segments in this playlist have programDateTime tags.
40115   *
40116   * @param {Object} playlist A playlist object
40117   */
40118
40119
40120  var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
40121    if (!playlist.segments || playlist.segments.length === 0) {
40122      return false;
40123    }
40124
40125    for (var i = 0; i < playlist.segments.length; i++) {
40126      var segment = playlist.segments[i];
40127
40128      if (!segment.dateTimeObject) {
40129        return false;
40130      }
40131    }
40132
40133    return true;
40134  };
40135  /**
40136   * Returns the programTime of the media given a playlist and a playerTime.
40137   * The playlist must have programDateTime tags for a programDateTime tag to be returned.
40138   * If the segments containing the time requested have not been buffered yet, an estimate
40139   * may be returned to the callback.
40140   *
40141   * @param {Object} args
40142   * @param {Object} args.playlist A playlist object to search within
40143   * @param {number} time A playerTime in seconds
40144   * @param {Function} callback(err, programTime)
40145   * @return {string} err.message A detailed error message
40146   * @return {Object} programTime
40147   * @return {number} programTime.mediaSeconds The streamTime in seconds
40148   * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
40149   */
40150
40151
40152  var getProgramTime = function getProgramTime(_ref) {
40153    var playlist = _ref.playlist,
40154        _ref$time = _ref.time,
40155        time = _ref$time === void 0 ? undefined : _ref$time,
40156        callback = _ref.callback;
40157
40158    if (!callback) {
40159      throw new Error('getProgramTime: callback must be provided');
40160    }
40161
40162    if (!playlist || time === undefined) {
40163      return callback({
40164        message: 'getProgramTime: playlist and time must be provided'
40165      });
40166    }
40167
40168    var matchedSegment = findSegmentForPlayerTime(time, playlist);
40169
40170    if (!matchedSegment) {
40171      return callback({
40172        message: 'valid programTime was not found'
40173      });
40174    }
40175
40176    if (matchedSegment.type === 'estimate') {
40177      return callback({
40178        message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
40179        seekTime: matchedSegment.estimatedStart
40180      });
40181    }
40182
40183    var programTimeObject = {
40184      mediaSeconds: time
40185    };
40186    var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
40187
40188    if (programTime) {
40189      programTimeObject.programDateTime = programTime.toISOString();
40190    }
40191
40192    return callback(null, programTimeObject);
40193  };
40194  /**
40195   * Seeks in the player to a time that matches the given programTime ISO-8601 string.
40196   *
40197   * @param {Object} args
40198   * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
40199   * @param {Object} args.playlist A playlist to look within
40200   * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
40201   * @param {Function} args.seekTo A method to perform a seek
40202   * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
40203   * @param {Object} args.tech The tech to seek on
40204   * @param {Function} args.callback(err, newTime) A callback to return the new time to
40205   * @return {string} err.message A detailed error message
40206   * @return {number} newTime The exact time that was seeked to in seconds
40207   */
40208
40209
40210  var seekToProgramTime = function seekToProgramTime(_ref2) {
40211    var programTime = _ref2.programTime,
40212        playlist = _ref2.playlist,
40213        _ref2$retryCount = _ref2.retryCount,
40214        retryCount = _ref2$retryCount === void 0 ? 2 : _ref2$retryCount,
40215        seekTo = _ref2.seekTo,
40216        _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
40217        pauseAfterSeek = _ref2$pauseAfterSeek === void 0 ? true : _ref2$pauseAfterSeek,
40218        tech = _ref2.tech,
40219        callback = _ref2.callback;
40220
40221    if (!callback) {
40222      throw new Error('seekToProgramTime: callback must be provided');
40223    }
40224
40225    if (typeof programTime === 'undefined' || !playlist || !seekTo) {
40226      return callback({
40227        message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
40228      });
40229    }
40230
40231    if (!playlist.endList && !tech.hasStarted_) {
40232      return callback({
40233        message: 'player must be playing a live stream to start buffering'
40234      });
40235    }
40236
40237    if (!verifyProgramDateTimeTags(playlist)) {
40238      return callback({
40239        message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
40240      });
40241    }
40242
40243    var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
40244
40245    if (!matchedSegment) {
40246      return callback({
40247        message: programTime + " was not found in the stream"
40248      });
40249    }
40250
40251    var segment = matchedSegment.segment;
40252    var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
40253
40254    if (matchedSegment.type === 'estimate') {
40255      // we've run out of retries
40256      if (retryCount === 0) {
40257        return callback({
40258          message: programTime + " is not buffered yet. Try again"
40259        });
40260      }
40261
40262      seekTo(matchedSegment.estimatedStart + mediaOffset);
40263      tech.one('seeked', function () {
40264        seekToProgramTime({
40265          programTime: programTime,
40266          playlist: playlist,
40267          retryCount: retryCount - 1,
40268          seekTo: seekTo,
40269          pauseAfterSeek: pauseAfterSeek,
40270          tech: tech,
40271          callback: callback
40272        });
40273      });
40274      return;
40275    } // Since the segment.start value is determined from the buffered end or ending time
40276    // of the prior segment, the seekToTime doesn't need to account for any transmuxer
40277    // modifications.
40278
40279
40280    var seekToTime = segment.start + mediaOffset;
40281
40282    var seekedCallback = function seekedCallback() {
40283      return callback(null, tech.currentTime());
40284    }; // listen for seeked event
40285
40286
40287    tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
40288
40289    if (pauseAfterSeek) {
40290      tech.pause();
40291    }
40292
40293    seekTo(seekToTime);
40294  }; // which will only happen if the request is complete.
40295
40296
40297  var callbackOnCompleted = function callbackOnCompleted(request, cb) {
40298    if (request.readyState === 4) {
40299      return cb();
40300    }
40301
40302    return;
40303  };
40304
40305  var containerRequest = function containerRequest(uri, xhr, cb) {
40306    var bytes = [];
40307    var id3Offset;
40308    var finished = false;
40309
40310    var endRequestAndCallback = function endRequestAndCallback(err, req, type, _bytes) {
40311      req.abort();
40312      finished = true;
40313      return cb(err, req, type, _bytes);
40314    };
40315
40316    var progressListener = function progressListener(error, request) {
40317      if (finished) {
40318        return;
40319      }
40320
40321      if (error) {
40322        return endRequestAndCallback(error, request, '', bytes);
40323      } // grap the new part of content that was just downloaded
40324
40325
40326      var newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
40327
40328      bytes = byteHelpers_2(bytes, byteHelpers_4(newPart, true));
40329      id3Offset = id3Offset || containers_2(bytes); // we need at least 10 bytes to determine a type
40330      // or we need at least two bytes after an id3Offset
40331
40332      if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
40333        return callbackOnCompleted(request, function () {
40334          return endRequestAndCallback(error, request, '', bytes);
40335        });
40336      }
40337
40338      var type = containers_1(bytes); // if this looks like a ts segment but we don't have enough data
40339      // to see the second sync byte, wait until we have enough data
40340      // before declaring it ts
40341
40342      if (type === 'ts' && bytes.length < 188) {
40343        return callbackOnCompleted(request, function () {
40344          return endRequestAndCallback(error, request, '', bytes);
40345        });
40346      } // this may be an unsynced ts segment
40347      // wait for 376 bytes before detecting no container
40348
40349
40350      if (!type && bytes.length < 376) {
40351        return callbackOnCompleted(request, function () {
40352          return endRequestAndCallback(error, request, '', bytes);
40353        });
40354      }
40355
40356      return endRequestAndCallback(null, request, type, bytes);
40357    };
40358
40359    var options = {
40360      uri: uri,
40361      beforeSend: function beforeSend(request) {
40362        // this forces the browser to pass the bytes to us unprocessed
40363        request.overrideMimeType('text/plain; charset=x-user-defined');
40364        request.addEventListener('progress', function (_ref) {
40365          var total = _ref.total,
40366              loaded = _ref.loaded;
40367          return callbackWrapper(request, null, {
40368            statusCode: request.status
40369          }, progressListener);
40370        });
40371      }
40372    };
40373    var request = xhr(options, function (error, response) {
40374      return callbackWrapper(request, error, response, progressListener);
40375    });
40376    return request;
40377  };
40378
40379  var EventTarget$1$1 = videojs$1.EventTarget,
40380      mergeOptions$2 = videojs$1.mergeOptions;
40381  /**
40382   * Parses the master XML string and updates playlist URI references.
40383   *
40384   * @param {Object} config
40385   *        Object of arguments
40386   * @param {string} config.masterXml
40387   *        The mpd XML
40388   * @param {string} config.srcUrl
40389   *        The mpd URL
40390   * @param {Date} config.clientOffset
40391   *         A time difference between server and client
40392   * @param {Object} config.sidxMapping
40393   *        SIDX mappings for moof/mdat URIs and byte ranges
40394   * @return {Object}
40395   *         The parsed mpd manifest object
40396   */
40397
40398  var parseMasterXml = function parseMasterXml(_ref) {
40399    var masterXml = _ref.masterXml,
40400        srcUrl = _ref.srcUrl,
40401        clientOffset = _ref.clientOffset,
40402        sidxMapping = _ref.sidxMapping;
40403    var master = parse(masterXml, {
40404      manifestUri: srcUrl,
40405      clientOffset: clientOffset,
40406      sidxMapping: sidxMapping
40407    });
40408    addPropertiesToMaster(master, srcUrl);
40409    return master;
40410  };
40411  /**
40412   * Returns a new master manifest that is the result of merging an updated master manifest
40413   * into the original version.
40414   *
40415   * @param {Object} oldMaster
40416   *        The old parsed mpd object
40417   * @param {Object} newMaster
40418   *        The updated parsed mpd object
40419   * @return {Object}
40420   *         A new object representing the original master manifest with the updated media
40421   *         playlists merged in
40422   */
40423
40424
40425  var updateMaster$1 = function updateMaster$1(oldMaster, newMaster) {
40426    var noChanges = true;
40427    var update = mergeOptions$2(oldMaster, {
40428      // These are top level properties that can be updated
40429      duration: newMaster.duration,
40430      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
40431    }); // First update the playlists in playlist list
40432
40433    for (var i = 0; i < newMaster.playlists.length; i++) {
40434      var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
40435
40436      if (playlistUpdate) {
40437        update = playlistUpdate;
40438        noChanges = false;
40439      }
40440    } // Then update media group playlists
40441
40442
40443    forEachMediaGroup(newMaster, function (properties, type, group, label) {
40444      if (properties.playlists && properties.playlists.length) {
40445        var id = properties.playlists[0].id;
40446
40447        var _playlistUpdate = updateMaster(update, properties.playlists[0]);
40448
40449        if (_playlistUpdate) {
40450          update = _playlistUpdate; // update the playlist reference within media groups
40451
40452          update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
40453          noChanges = false;
40454        }
40455      }
40456    });
40457
40458    if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
40459      noChanges = false;
40460    }
40461
40462    if (noChanges) {
40463      return null;
40464    }
40465
40466    return update;
40467  };
40468
40469  var generateSidxKey = function generateSidxKey(sidxInfo) {
40470    // should be non-inclusive
40471    var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
40472    return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
40473  }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
40474  // If the SIDXs have maps, the two maps should match,
40475  // both `a` and `b` missing SIDXs is considered matching.
40476  // If `a` or `b` but not both have a map, they aren't matching.
40477
40478
40479  var equivalentSidx = function equivalentSidx(a, b) {
40480    var neitherMap = Boolean(!a.map && !b.map);
40481    var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
40482    return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
40483  }; // exported for testing
40484
40485
40486  var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
40487    var newSidxMapping = {};
40488
40489    for (var id in playlists) {
40490      var playlist = playlists[id];
40491      var currentSidxInfo = playlist.sidx;
40492
40493      if (currentSidxInfo) {
40494        var key = generateSidxKey(currentSidxInfo);
40495
40496        if (!oldSidxMapping[key]) {
40497          break;
40498        }
40499
40500        var savedSidxInfo = oldSidxMapping[key].sidxInfo;
40501
40502        if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
40503          newSidxMapping[key] = oldSidxMapping[key];
40504        }
40505      }
40506    }
40507
40508    return newSidxMapping;
40509  };
40510  /**
40511   *  A function that filters out changed items as they need to be requested separately.
40512   *
40513   *  The method is exported for testing
40514   *
40515   *  @param {Object} masterXml the mpd XML
40516   *  @param {string} srcUrl the mpd url
40517   *  @param {Date} clientOffset a time difference between server and client (passed through and not used)
40518   *  @param {Object} oldSidxMapping the SIDX to compare against
40519   */
40520
40521
40522  var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
40523    // Don't pass current sidx mapping
40524    var master = parse(masterXml, {
40525      manifestUri: srcUrl,
40526      clientOffset: clientOffset
40527    });
40528    var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
40529    var mediaGroupSidx = videoSidx;
40530    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
40531      if (properties.playlists && properties.playlists.length) {
40532        var playlists = properties.playlists;
40533        mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
40534      }
40535    });
40536    return mediaGroupSidx;
40537  }; // exported for testing
40538
40539
40540  var requestSidx_ = function requestSidx_(loader, sidxRange, playlist, xhr, options, finishProcessingFn) {
40541    var sidxInfo = {
40542      // resolve the segment URL relative to the playlist
40543      uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
40544      // resolvedUri: sidxRange.resolvedUri,
40545      byterange: sidxRange.byterange,
40546      // the segment's playlist
40547      playlist: playlist
40548    };
40549    var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
40550      responseType: 'arraybuffer',
40551      headers: segmentXhrHeaders(sidxInfo)
40552    });
40553    return containerRequest(sidxInfo.uri, xhr, function (err, request, container, bytes) {
40554      if (err) {
40555        return finishProcessingFn(err, request);
40556      }
40557
40558      if (!container || container !== 'mp4') {
40559        return finishProcessingFn({
40560          status: request.status,
40561          message: "Unsupported " + (container || 'unknown') + " container type for sidx segment at URL: " + sidxInfo.uri,
40562          // response is just bytes in this case
40563          // but we really don't want to return that.
40564          response: '',
40565          playlist: playlist,
40566          internal: true,
40567          blacklistDuration: Infinity,
40568          // MEDIA_ERR_NETWORK
40569          code: 2
40570        }, request);
40571      } // if we already downloaded the sidx bytes in the container request, use them
40572
40573
40574      var _sidxInfo$byterange = sidxInfo.byterange,
40575          offset = _sidxInfo$byterange.offset,
40576          length = _sidxInfo$byterange.length;
40577
40578      if (bytes.length >= length + offset) {
40579        return finishProcessingFn(err, {
40580          response: bytes.subarray(offset, offset + length),
40581          status: request.status,
40582          uri: request.uri
40583        });
40584      } // otherwise request sidx bytes
40585
40586
40587      loader.request = xhr(sidxRequestOptions, finishProcessingFn);
40588    });
40589  };
40590
40591  var DashPlaylistLoader = /*#__PURE__*/function (_EventTarget) {
40592    inheritsLoose(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
40593    // playlist loader setups from media groups will expect to be able to pass a playlist
40594    // (since there aren't external URLs to media playlists with DASH)
40595
40596
40597    function DashPlaylistLoader(srcUrlOrPlaylist, vhs, options, masterPlaylistLoader) {
40598      var _this;
40599
40600      if (options === void 0) {
40601        options = {};
40602      }
40603
40604      _this = _EventTarget.call(this) || this;
40605      var _options = options,
40606          _options$withCredenti = _options.withCredentials,
40607          withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
40608          _options$handleManife = _options.handleManifestRedirects,
40609          handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
40610      _this.vhs_ = vhs;
40611      _this.withCredentials = withCredentials;
40612      _this.handleManifestRedirects = handleManifestRedirects;
40613
40614      if (!srcUrlOrPlaylist) {
40615        throw new Error('A non-empty playlist URL or object is required');
40616      } // event naming?
40617
40618
40619      _this.on('minimumUpdatePeriod', function () {
40620        _this.refreshXml_();
40621      }); // live playlist staleness timeout
40622
40623
40624      _this.on('mediaupdatetimeout', function () {
40625        _this.refreshMedia_(_this.media().id);
40626      });
40627
40628      _this.state = 'HAVE_NOTHING';
40629      _this.loadedPlaylists_ = {}; // initialize the loader state
40630      // The masterPlaylistLoader will be created with a string
40631
40632      if (typeof srcUrlOrPlaylist === 'string') {
40633        _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
40634        // once multi-period is refactored
40635
40636        _this.sidxMapping_ = {};
40637        return assertThisInitialized(_this);
40638      }
40639
40640      _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
40641
40642      return _this;
40643    }
40644
40645    var _proto = DashPlaylistLoader.prototype;
40646
40647    _proto.setupChildLoader = function setupChildLoader(masterPlaylistLoader, playlist) {
40648      this.masterPlaylistLoader_ = masterPlaylistLoader;
40649      this.childPlaylist_ = playlist;
40650    };
40651
40652    _proto.dispose = function dispose() {
40653      this.trigger('dispose');
40654      this.stopRequest();
40655      this.loadedPlaylists_ = {};
40656      window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
40657      window$1.clearTimeout(this.mediaRequest_);
40658      window$1.clearTimeout(this.mediaUpdateTimeout);
40659      this.off();
40660    };
40661
40662    _proto.hasPendingRequest = function hasPendingRequest() {
40663      return this.request || this.mediaRequest_;
40664    };
40665
40666    _proto.stopRequest = function stopRequest() {
40667      if (this.request) {
40668        var oldRequest = this.request;
40669        this.request = null;
40670        oldRequest.onreadystatechange = null;
40671        oldRequest.abort();
40672      }
40673    };
40674
40675    _proto.sidxRequestFinished_ = function sidxRequestFinished_(playlist, master, startingState, doneFn) {
40676      var _this2 = this;
40677
40678      return function (err, request) {
40679        // disposed
40680        if (!_this2.request) {
40681          return;
40682        } // pending request is cleared
40683
40684
40685        _this2.request = null;
40686
40687        if (err) {
40688          // use the provided error or create one
40689          // see requestSidx_ for the container request
40690          // that can cause this.
40691          _this2.error = typeof err === 'object' ? err : {
40692            status: request.status,
40693            message: 'DASH playlist request error at URL: ' + playlist.uri,
40694            response: request.response,
40695            // MEDIA_ERR_NETWORK
40696            code: 2
40697          };
40698
40699          if (startingState) {
40700            _this2.state = startingState;
40701          }
40702
40703          _this2.trigger('error');
40704
40705          return;
40706        }
40707
40708        var bytes = byteHelpers_5(request.response);
40709        var sidx = parseSidx_1(bytes.subarray(8));
40710        return doneFn(master, sidx);
40711      };
40712    };
40713
40714    _proto.media = function media(playlist) {
40715      var _this3 = this; // getter
40716
40717
40718      if (!playlist) {
40719        return this.media_;
40720      } // setter
40721
40722
40723      if (this.state === 'HAVE_NOTHING') {
40724        throw new Error('Cannot switch media playlist from ' + this.state);
40725      }
40726
40727      var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
40728
40729      if (typeof playlist === 'string') {
40730        if (!this.master.playlists[playlist]) {
40731          throw new Error('Unknown playlist URI: ' + playlist);
40732        }
40733
40734        playlist = this.master.playlists[playlist];
40735      }
40736
40737      var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
40738
40739      if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
40740        this.state = 'HAVE_METADATA';
40741        this.media_ = playlist; // trigger media change if the active media has been updated
40742
40743        if (mediaChange) {
40744          this.trigger('mediachanging');
40745          this.trigger('mediachange');
40746        }
40747
40748        return;
40749      } // switching to the active playlist is a no-op
40750
40751
40752      if (!mediaChange) {
40753        return;
40754      } // switching from an already loaded playlist
40755
40756
40757      if (this.media_) {
40758        this.trigger('mediachanging');
40759      }
40760
40761      if (!playlist.sidx) {
40762        // Continue asynchronously if there is no sidx
40763        // wait one tick to allow haveMaster to run first on a child loader
40764        this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
40765          startingState: startingState,
40766          playlist: playlist
40767        }), 0); // exit early and don't do sidx work
40768
40769        return;
40770      } // we have sidx mappings
40771
40772
40773      var oldMaster;
40774      var sidxMapping; // sidxMapping is used when parsing the masterXml, so store
40775      // it on the masterPlaylistLoader
40776
40777      if (this.masterPlaylistLoader_) {
40778        oldMaster = this.masterPlaylistLoader_.master;
40779        sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
40780      } else {
40781        oldMaster = this.master;
40782        sidxMapping = this.sidxMapping_;
40783      }
40784
40785      var sidxKey = generateSidxKey(playlist.sidx);
40786      sidxMapping[sidxKey] = {
40787        sidxInfo: playlist.sidx
40788      };
40789      this.request = requestSidx_(this, playlist.sidx, playlist, this.vhs_.xhr, {
40790        handleManifestRedirects: this.handleManifestRedirects
40791      }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
40792        if (!newMaster || !sidx) {
40793          throw new Error('failed to request sidx');
40794        } // update loader's sidxMapping with parsed sidx box
40795
40796
40797        sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
40798
40799        _this3.haveMetadata({
40800          startingState: startingState,
40801          playlist: newMaster.playlists[playlist.id]
40802        });
40803      }));
40804    };
40805
40806    _proto.haveMetadata = function haveMetadata(_ref2) {
40807      var startingState = _ref2.startingState,
40808          playlist = _ref2.playlist;
40809      this.state = 'HAVE_METADATA';
40810      this.loadedPlaylists_[playlist.id] = playlist;
40811      this.mediaRequest_ = null; // This will trigger loadedplaylist
40812
40813      this.refreshMedia_(playlist.id);
40813 // fire loadedmetadata the first time a media playlist is loaded
40814      // to resolve setup of media groups
40815
40816      if (startingState === 'HAVE_MASTER') {
40817        this.trigger('loadedmetadata');
40818      } else {
40819        // trigger media change if the active media has been updated
40820        this.trigger('mediachange');
40821      }
40822    };
40823
40824    _proto.pause = function pause() {
40825      this.stopRequest();
40826      window$1.clearTimeout(this.mediaUpdateTimeout);
40827      window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
40828
40829      if (this.state === 'HAVE_NOTHING') {
40830        // If we pause the loader before any data has been retrieved, its as if we never
40831        // started, so reset to an unstarted state.
40832        this.started = false;
40833      }
40834    };
40835
40836    _proto.load = function load(isFinalRendition) {
40837      var _this4 = this;
40838
40839      window$1.clearTimeout(this.mediaUpdateTimeout);
40840      window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
40841      var media = this.media();
40842
40843      if (isFinalRendition) {
40844        var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
40845        this.mediaUpdateTimeout = window$1.setTimeout(function () {
40846          return _this4.load();
40847        }, delay);
40848        return;
40849      } // because the playlists are internal to the manifest, load should either load the
40850      // main manifest, or do nothing but trigger an event
40851
40852
40853      if (!this.started) {
40854        this.start();
40855        return;
40856      }
40857
40858      if (media && !media.endList) {
40859        this.trigger('mediaupdatetimeout');
40860      } else {
40861        this.trigger('loadedplaylist');
40862      }
40863    };
40864
40865    _proto.start = function start() {
40866      var _this5 = this;
40867
40868      this.started = true; // We don't need to request the master manifest again
40869      // Call this asynchronously to match the xhr request behavior below
40870
40871      if (this.masterPlaylistLoader_) {
40872        this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
40873        return;
40874      } // request the specified URL
40875
40876
40877      this.request = this.vhs_.xhr({
40878        uri: this.srcUrl,
40879        withCredentials: this.withCredentials
40880      }, function (error, req) {
40881        // disposed
40882        if (!_this5.request) {
40883          return;
40884        } // clear the loader's request reference
40885
40886
40887        _this5.request = null;
40888
40889        if (error) {
40890          _this5.error = {
40891            status: req.status,
40892            message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
40893            responseText: req.responseText,
40894            // MEDIA_ERR_NETWORK
40895            code: 2
40896          };
40897
40898          if (_this5.state === 'HAVE_NOTHING') {
40899            _this5.started = false;
40900          }
40901
40902          return _this5.trigger('error');
40903        }
40904
40905        _this5.masterXml_ = req.responseText;
40906
40907        if (req.responseHeaders && req.responseHeaders.date) {
40908          _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
40909        } else {
40910          _this5.masterLoaded_ = Date.now();
40911        }
40912
40913        _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
40914
40915        _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
40916      });
40917    }
40918    /**
40919     * Parses the master xml for UTCTiming node to sync the client clock to the server
40920     * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
40921     *
40922     * @param {Function} done
40923     *        Function to call when clock sync has completed
40924     */
40925    ;
40926
40927    _proto.syncClientServerClock_ = function syncClientServerClock_(done) {
40928      var _this6 = this;
40929
40930      var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
40931      // server clock
40932
40933      if (utcTiming === null) {
40934        this.clientOffset_ = this.masterLoaded_ - Date.now();
40935        return done();
40936      }
40937
40938      if (utcTiming.method === 'DIRECT') {
40939        this.clientOffset_ = utcTiming.value - Date.now();
40940        return done();
40941      }
40942
40943      this.request = this.vhs_.xhr({
40944        uri: resolveUrl$1(this.srcUrl, utcTiming.value),
40945        method: utcTiming.method,
40946        withCredentials: this.withCredentials
40947      }, function (error, req) {
40948        // disposed
40949        if (!_this6.request) {
40950          return;
40951        }
40952
40953        if (error) {
40954          // sync request failed, fall back to using date header from mpd
40955          // TODO: log warning
40956          _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
40957          return done();
40958        }
40959
40960        var serverTime;
40961
40962        if (utcTiming.method === 'HEAD') {
40963          if (!req.responseHeaders || !req.responseHeaders.date) {
40964            // expected date header not preset, fall back to using date header from mpd
40965            // TODO: log warning
40966            serverTime = _this6.masterLoaded_;
40967          } else {
40968            serverTime = Date.parse(req.responseHeaders.date);
40969          }
40970        } else {
40971          serverTime = Date.parse(req.responseText);
40972        }
40973
40974        _this6.clientOffset_ = serverTime - Date.now();
40975        done();
40976      });
40977    };
40978
40979    _proto.haveMaster_ = function haveMaster_() {
40980      this.state = 'HAVE_MASTER'; // clear media request
40981
40982      this.mediaRequest_ = null;
40983
40984      if (!this.masterPlaylistLoader_) {
40985        this.updateMainManifest_(parseMasterXml({
40986          masterXml: this.masterXml_,
40987          srcUrl: this.srcUrl,
40988          clientOffset: this.clientOffset_,
40989          sidxMapping: this.sidxMapping_
40990        })); // We have the master playlist at this point, so
40991        // trigger this to allow MasterPlaylistController
40992        // to make an initial playlist selection
40993
40994        this.trigger('loadedplaylist');
40995      } else if (!this.media_) {
40996        // no media playlist was specifically selected so select
40997        // the one the child playlist loader was created with
40998        this.media(this.childPlaylist_);
40999      }
41000    };
41001
41002    _proto.updateMinimumUpdatePeriodTimeout_ = function updateMinimumUpdatePeriodTimeout_() {
41003      var _this7 = this; // Clear existing timeout
41004
41005
41006      window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
41007
41008      var createMUPTimeout = function createMUPTimeout(mup) {
41009        _this7.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
41010          _this7.trigger('minimumUpdatePeriod');
41011        }, mup);
41012      };
41013
41014      var minimumUpdatePeriod = this.master && this.master.minimumUpdatePeriod;
41015
41016      if (minimumUpdatePeriod > 0) {
41017        createMUPTimeout(minimumUpdatePeriod); // If the minimumUpdatePeriod has a value of 0, that indicates that the current
41018        // MPD has no future validity, so a new one will need to be acquired when new
41019        // media segments are to be made available. Thus, we use the target duration
41020        // in this case
41021      } else if (minimumUpdatePeriod === 0) {
41022        // If we haven't yet selected a playlist, wait until then so we know the
41023        // target duration
41024        if (!this.media()) {
41025          this.one('loadedplaylist', function () {
41026            createMUPTimeout(_this7.media().targetDuration * 1000);
41027          });
41028        } else {
41029          createMUPTimeout(this.media().targetDuration * 1000);
41030        }
41031      }
41032    }
41033    /**
41034     * Handler for after client/server clock synchronization has happened. Sets up
41035     * xml refresh timer if specificed by the manifest.
41036     */
41037    ;
41038
41039    _proto.onClientServerClockSync_ = function onClientServerClockSync_() {
41040      this.haveMaster_();
41041
41042      if (!this.hasPendingRequest() && !this.media_) {
41043        this.media(this.master.playlists[0]);
41044      }
41045
41046      this.updateMinimumUpdatePeriodTimeout_();
41047    }
41048    /**
41049     * Given a new manifest, update our pointer to it and update the srcUrl based on the location elements of the manifest, if they exist.
41050     *
41051     * @param {Object} updatedManifest the manifest to update to
41052     */
41053    ;
41054
41055    _proto.updateMainManifest_ = function updateMainManifest_(updatedManifest) {
41056      this.master = updatedManifest; // if locations isn't set or is an empty array, exit early
41057
41058      if (!this.master.locations || !this.master.locations.length) {
41059        return;
41060      }
41061
41062      var location = this.master.locations[0];
41063
41064      if (location !== this.srcUrl) {
41065        this.srcUrl = location;
41066      }
41067    }
41068    /**
41069     * Sends request to refresh the master xml and updates the parsed master manifest
41070     * TODO: Does the client offset need to be recalculated when the xml is refreshed?
41071     */
41072    ;
41073
41074    _proto.refreshXml_ = function refreshXml_() {
41075      var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
41076      // sidx is implemented
41077
41078
41079      this.request = this.vhs_.xhr({
41080        uri: this.srcUrl,
41081        withCredentials: this.withCredentials
41082      }, function (error, req) {
41083        // disposed
41084        if (!_this8.request) {
41085          return;
41086        } // clear the loader's request reference
41087
41088
41089        _this8.request = null;
41090
41091        if (error) {
41092          _this8.error = {
41093            status: req.status,
41094            message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
41095            responseText: req.responseText,
41096            // MEDIA_ERR_NETWORK
41097            code: 2
41098          };
41099
41100          if (_this8.state === 'HAVE_NOTHING') {
41101            _this8.started = false;
41102          }
41103
41104          return _this8.trigger('error');
41105        }
41106
41107        _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
41108
41109        _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
41110        var master = parseMasterXml({
41111          masterXml: _this8.masterXml_,
41112          srcUrl: _this8.srcUrl,
41113          clientOffset: _this8.clientOffset_,
41114          sidxMapping: _this8.sidxMapping_
41115        });
41116        var updatedMaster = updateMaster$1(_this8.master, master);
41117
41118        var currentSidxInfo = _this8.media().sidx;
41119
41120        if (updatedMaster) {
41121          if (currentSidxInfo) {
41122            var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed
41123
41124            if (!_this8.sidxMapping_[sidxKey]) {
41125              var playlist = _this8.media();
41126
41127              _this8.request = requestSidx_(_this8, playlist.sidx, playlist, _this8.vhs_.xhr, {
41128                handleManifestRedirects: _this8.handleManifestRedirects
41129              }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
41130                if (!newMaster || !sidx) {
41131                  throw new Error('failed to request sidx on minimumUpdatePeriod');
41132                } // update loader's sidxMapping with parsed sidx box
41133
41134
41135                _this8.sidxMapping_[sidxKey].sidx = sidx;
41136
41137                _this8.updateMinimumUpdatePeriodTimeout_(); // TODO: do we need to reload the current playlist?
41138
41139
41140                _this8.refreshMedia_(_this8.media().id);
41141
41142                return;
41143              }));
41144            }
41145          } else {
41146            _this8.updateMainManifest_(updatedMaster);
41147
41148            if (_this8.media_) {
41149              _this8.media_ = _this8.master.playlists[_this8.media_.id];
41150            }
41151          }
41152        }
41153
41154        _this8.updateMinimumUpdatePeriodTimeout_();
41155      });
41156    }
41157    /**
41158     * Refreshes the media playlist by re-parsing the master xml and updating playlist
41159     * references. If this is an alternate loader, the updated parsed manifest is retrieved
41160     * from the master loader.
41161     */
41162    ;
41163
41164    _proto.refreshMedia_ = function refreshMedia_(mediaID) {
41165      var _this9 = this;
41166
41167      if (!mediaID) {
41168        throw new Error('refreshMedia_ must take a media id');
41169      }
41170
41171      var oldMaster;
41172      var newMaster;
41173
41174      if (this.masterPlaylistLoader_) {
41175        oldMaster = this.masterPlaylistLoader_.master;
41176        newMaster = parseMasterXml({
41177          masterXml: this.masterPlaylistLoader_.masterXml_,
41178          srcUrl: this.masterPlaylistLoader_.srcUrl,
41179          clientOffset: this.masterPlaylistLoader_.clientOffset_,
41180          sidxMapping: this.masterPlaylistLoader_.sidxMapping_
41181        });
41182      } else {
41183        oldMaster = this.master;
41184        newMaster = parseMasterXml({
41185          masterXml: this.masterXml_,
41186          srcUrl: this.srcUrl,
41187          clientOffset: this.clientOffset_,
41188          sidxMapping: this.sidxMapping_
41189        });
41190      }
41191
41192      var updatedMaster = updateMaster$1(oldMaster, newMaster);
41193
41194      if (updatedMaster) {
41195        if (this.masterPlaylistLoader_) {
41196          this.masterPlaylistLoader_.master = updatedMaster;
41197        } else {
41198          this.master = updatedMaster;
41199        }
41200
41201        this.media_ = updatedMaster.playlists[mediaID];
41202      } else {
41203        this.media_ = oldMaster.playlists[mediaID];
41204        this.trigger('playlistunchanged');
41205      }
41206
41207      if (!this.media().endList) {
41208        this.mediaUpdateTimeout = window$1.setTimeout(function () {
41209          _this9.trigger('mediaupdatetimeout');
41210        }, refreshDelay(this.media(), !!updatedMaster));
41211      }
41212
41213      this.trigger('loadedplaylist');
41214    };
41215
41216    return DashPlaylistLoader;
41217  }(EventTarget$1$1);
41218
41219  var Config = {
41220    GOAL_BUFFER_LENGTH: 30,
41221    MAX_GOAL_BUFFER_LENGTH: 60,
41222    BACK_BUFFER_LENGTH: 30,
41223    GOAL_BUFFER_LENGTH_RATE: 1,
41224    // 0.5 MB/s
41225    INITIAL_BANDWIDTH: 4194304,
41226    // A fudge factor to apply to advertised playlist bitrates to account for
41227    // temporary flucations in client bandwidth
41228    BANDWIDTH_VARIANCE: 1.2,
41229    // How much of the buffer must be filled before we consider upswitching
41230    BUFFER_LOW_WATER_LINE: 0,
41231    MAX_BUFFER_LOW_WATER_LINE: 30,
41232    BUFFER_LOW_WATER_LINE_RATE: 1
41233  };
41234
41235  var stringToArrayBuffer = function stringToArrayBuffer(string) {
41236    var view = new Uint8Array(new ArrayBuffer(string.length));
41237
41238    for (var i = 0; i < string.length; i++) {
41239      view[i] = string.charCodeAt(i);
41240    }
41241
41242    return view.buffer;
41243  };
41244
41245  var transmuxQueue = [];
41246  var currentTransmux;
41247
41248  var handleData_ = function handleData_(event, transmuxedData, callback) {
41249    var _event$data$segment = event.data.segment,
41250        type = _event$data$segment.type,
41251        initSegment = _event$data$segment.initSegment,
41252        captions = _event$data$segment.captions,
41253        captionStreams = _event$data$segment.captionStreams,
41254        metadata = _event$data$segment.metadata,
41255        videoFrameDtsTime = _event$data$segment.videoFrameDtsTime,
41256        videoFramePtsTime = _event$data$segment.videoFramePtsTime;
41257    transmuxedData.buffer.push({
41258      captions: captions,
41259      captionStreams: captionStreams,
41260      metadata: metadata
41261    }); // right now, boxes will come back from partial transmuxer, data from full
41262
41263    var boxes = event.data.segment.boxes || {
41264      data: event.data.segment.data
41265    };
41266    var result = {
41267      type: type,
41268      // cast ArrayBuffer to TypedArray
41269      data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
41270      initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
41271    };
41272
41273    if (typeof videoFrameDtsTime !== 'undefined') {
41274      result.videoFrameDtsTime = videoFrameDtsTime;
41275    }
41276
41277    if (typeof videoFramePtsTime !== 'undefined') {
41278      result.videoFramePtsTime = videoFramePtsTime;
41279    }
41280
41281    callback(result);
41282  };
41283
41284  var handleDone_ = function handleDone_(_ref) {
41285    var transmuxedData = _ref.transmuxedData,
41286        callback = _ref.callback; // Previously we only returned data on data events,
41287    // not on done events. Clear out the buffer to keep that consistent.
41288
41289    transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
41290    // have received
41291
41292    callback(transmuxedData);
41293  };
41294
41295  var handleGopInfo_ = function handleGopInfo_(event, transmuxedData) {
41296    transmuxedData.gopInfo = event.data.gopInfo;
41297  };
41298
41299  var processTransmux = function processTransmux(_ref2) {
41300    var transmuxer = _ref2.transmuxer,
41301        bytes = _ref2.bytes,
41302        audioAppendStart = _ref2.audioAppendStart,
41303        gopsToAlignWith = _ref2.gopsToAlignWith,
41304        isPartial = _ref2.isPartial,
41305        remux = _ref2.remux,
41306        onData = _ref2.onData,
41307        onTrackInfo = _ref2.onTrackInfo,
41308        onAudioTimingInfo = _ref2.onAudioTimingInfo,
41309        onVideoTimingInfo = _ref2.onVideoTimingInfo,
41310        onVideoSegmentTimingInfo = _ref2.onVideoSegmentTimingInfo,
41311        onId3 = _ref2.onId3,
41312        onCaptions = _ref2.onCaptions,
41313        onDone = _ref2.onDone;
41314    var transmuxedData = {
41315      isPartial: isPartial,
41316      buffer: []
41317    };
41318
41319    var handleMessage = function handleMessage(event) {
41320      if (!currentTransmux) {
41321        // disposed
41322        return;
41323      }
41324
41325      if (event.data.action === 'data') {
41326        handleData_(event, transmuxedData, onData);
41327      }
41328
41329      if (event.data.action === 'trackinfo') {
41330        onTrackInfo(event.data.trackInfo);
41331      }
41332
41333      if (event.data.action === 'gopInfo') {
41334        handleGopInfo_(event, transmuxedData);
41335      }
41336
41337      if (event.data.action === 'audioTimingInfo') {
41338        onAudioTimingInfo(event.data.audioTimingInfo);
41339      }
41340
41341      if (event.data.action === 'videoTimingInfo') {
41342        onVideoTimingInfo(event.data.videoTimingInfo);
41343      }
41344
41345      if (event.data.action === 'videoSegmentTimingInfo') {
41346        onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
41347      }
41348
41349      if (event.data.action === 'id3Frame') {
41350        onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
41351      }
41352
41353      if (event.data.action === 'caption') {
41354        onCaptions(event.data.caption);
41355      } // wait for the transmuxed event since we may have audio and video
41356
41357
41358      if (event.data.type !== 'transmuxed') {
41359        return;
41360      }
41361
41362      transmuxer.onmessage = null;
41363      handleDone_({
41364        transmuxedData: transmuxedData,
41365        callback: onDone
41366      });
41367      /* eslint-disable no-use-before-define */
41368
41369      dequeue();
41370      /* eslint-enable */
41371    };
41372
41373    transmuxer.onmessage = handleMessage;
41374
41375    if (audioAppendStart) {
41376      transmuxer.postMessage({
41377        action: 'setAudioAppendStart',
41378        appendStart: audioAppendStart
41379      });
41380    } // allow empty arrays to be passed to clear out GOPs
41381
41382
41383    if (Array.isArray(gopsToAlignWith)) {
41384      transmuxer.postMessage({
41385        action: 'alignGopsWith',
41386        gopsToAlignWith: gopsToAlignWith
41387      });
41388    }
41389
41390    if (typeof remux !== 'undefined') {
41391      transmuxer.postMessage({
41392        action: 'setRemux',
41393        remux: remux
41394      });
41395    }
41396
41397    if (bytes.byteLength) {
41398      var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
41399      var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
41400      transmuxer.postMessage({
41401        action: 'push',
41402        // Send the typed-array of data as an ArrayBuffer so that
41403        // it can be sent as a "Transferable" and avoid the costly
41404        // memory copy
41405        data: buffer,
41406        // To recreate the original typed-array, we need information
41407        // about what portion of the ArrayBuffer it was a view into
41408        byteOffset: byteOffset,
41409        byteLength: bytes.byteLength
41410      }, [buffer]);
41411    } // even if we didn't push any bytes, we have to make sure we flush in case we reached
41412    // the end of the segment
41413
41414
41415    transmuxer.postMessage({
41416      action: isPartial ? 'partialFlush' : 'flush'
41417    });
41418  };
41419
41420  var dequeue = function dequeue() {
41421    currentTransmux = null;
41422
41423    if (transmuxQueue.length) {
41424      currentTransmux = transmuxQueue.shift();
41425
41426      if (typeof currentTransmux === 'function') {
41427        currentTransmux();
41428      } else {
41429        processTransmux(currentTransmux);
41430      }
41431    }
41432  };
41433
41434  var processAction = function processAction(transmuxer, action) {
41435    transmuxer.postMessage({
41436      action: action
41437    });
41438    dequeue();
41439  };
41440
41441  var enqueueAction = function enqueueAction(action, transmuxer) {
41442    if (!currentTransmux) {
41443      currentTransmux = action;
41444      processAction(transmuxer, action);
41445      return;
41446    }
41447
41448    transmuxQueue.push(processAction.bind(null, transmuxer, action));
41449  };
41450
41451  var reset = function reset(transmuxer) {
41452    enqueueAction('reset', transmuxer);
41453  };
41454
41455  var endTimeline = function endTimeline(transmuxer) {
41456    enqueueAction('endTimeline', transmuxer);
41457  };
41458
41459  var transmux = function transmux(options) {
41460    if (!currentTransmux) {
41461      currentTransmux = options;
41462      processTransmux(options);
41463      return;
41464    }
41465
41466    transmuxQueue.push(options);
41467  };
41468
41469  var dispose = function dispose() {
41470    // clear out module-level references
41471    currentTransmux = null;
41472    transmuxQueue.length = 0;
41473  };
41474
41475  var segmentTransmuxer = {
41476    reset: reset,
vendor: 15,580 bytes, lines 41477-41934
41477    dispose: dispose,
41478    endTimeline: endTimeline,
41479    transmux: transmux
41480  };
41481  /**
41482   * Probe an mpeg2-ts segment to determine the start time of the segment in it's
41483   * internal "media time," as well as whether it contains video and/or audio.
41484   *
41485   * @private
41486   * @param {Uint8Array} bytes - segment bytes
41487   * @return {Object} The start time of the current segment in "media time" as well as
41488   *                  whether it contains video and/or audio
41489   */
41490
41491  var probeTsSegment = function probeTsSegment(bytes, baseStartTime) {
41492    var timeInfo = tsInspector.inspect(bytes, baseStartTime * clock_1);
41493
41494    if (!timeInfo) {
41495      return null;
41496    }
41497
41498    var result = {
41499      // each type's time info comes back as an array of 2 times, start and end
41500      hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
41501      hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
41502    };
41503
41504    if (result.hasVideo) {
41505      result.videoStart = timeInfo.video[0].ptsTime;
41506    }
41507
41508    if (result.hasAudio) {
41509      result.audioStart = timeInfo.audio[0].ptsTime;
41510    }
41511
41512    return result;
41513  };
41514  /**
41515   * Combine all segments into a single Uint8Array
41516   *
41517   * @param {Object} segmentObj
41518   * @return {Uint8Array} concatenated bytes
41519   * @private
41520   */
41521
41522
41523  var concatSegments = function concatSegments(segmentObj) {
41524    var offset = 0;
41525    var tempBuffer;
41526
41527    if (segmentObj.bytes) {
41528      tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
41529
41530      segmentObj.segments.forEach(function (segment) {
41531        tempBuffer.set(segment, offset);
41532        offset += segment.byteLength;
41533      });
41534    }
41535
41536    return tempBuffer;
41537  };
41538
41539  var REQUEST_ERRORS = {
41540    FAILURE: 2,
41541    TIMEOUT: -101,
41542    ABORTED: -102
41543  };
41544  /**
41545   * Abort all requests
41546   *
41547   * @param {Object} activeXhrs - an object that tracks all XHR requests
41548   */
41549
41550  var abortAll = function abortAll(activeXhrs) {
41551    activeXhrs.forEach(function (xhr) {
41552      xhr.abort();
41553    });
41554  };
41555  /**
41556   * Gather important bandwidth stats once a request has completed
41557   *
41558   * @param {Object} request - the XHR request from which to gather stats
41559   */
41560
41561
41562  var getRequestStats = function getRequestStats(request) {
41563    return {
41564      bandwidth: request.bandwidth,
41565      bytesReceived: request.bytesReceived || 0,
41566      roundTripTime: request.roundTripTime || 0
41567    };
41568  };
41569  /**
41570   * If possible gather bandwidth stats as a request is in
41571   * progress
41572   *
41573   * @param {Event} progressEvent - an event object from an XHR's progress event
41574   */
41575
41576
41577  var getProgressStats = function getProgressStats(progressEvent) {
41578    var request = progressEvent.target;
41579    var roundTripTime = Date.now() - request.requestTime;
41580    var stats = {
41581      bandwidth: Infinity,
41582      bytesReceived: 0,
41583      roundTripTime: roundTripTime || 0
41584    };
41585    stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
41586    // because we should only use bandwidth stats on progress to determine when
41587    // abort a request early due to insufficient bandwidth
41588
41589    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
41590    return stats;
41591  };
41592  /**
41593   * Handle all error conditions in one place and return an object
41594   * with all the information
41595   *
41596   * @param {Error|null} error - if non-null signals an error occured with the XHR
41597   * @param {Object} request -  the XHR request that possibly generated the error
41598   */
41599
41600
41601  var handleErrors = function handleErrors(error, request) {
41602    if (request.timedout) {
41603      return {
41604        status: request.status,
41605        message: 'HLS request timed-out at URL: ' + request.uri,
41606        code: REQUEST_ERRORS.TIMEOUT,
41607        xhr: request
41608      };
41609    }
41610
41611    if (request.aborted) {
41612      return {
41613        status: request.status,
41614        message: 'HLS request aborted at URL: ' + request.uri,
41615        code: REQUEST_ERRORS.ABORTED,
41616        xhr: request
41617      };
41618    }
41619
41620    if (error) {
41621      return {
41622        status: request.status,
41623        message: 'HLS request errored at URL: ' + request.uri,
41624        code: REQUEST_ERRORS.FAILURE,
41625        xhr: request
41626      };
41627    }
41628
41629    return null;
41630  };
41631  /**
41632   * Handle responses for key data and convert the key data to the correct format
41633   * for the decryption step later
41634   *
41635   * @param {Object} segment - a simplified copy of the segmentInfo object
41636   *                           from SegmentLoader
41637   * @param {Function} finishProcessingFn - a callback to execute to continue processing
41638   *                                        this request
41639   */
41640
41641
41642  var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
41643    return function (error, request) {
41644      var response = request.response;
41645      var errorObj = handleErrors(error, request);
41646
41647      if (errorObj) {
41648        return finishProcessingFn(errorObj, segment);
41649      }
41650
41651      if (response.byteLength !== 16) {
41652        return finishProcessingFn({
41653          status: request.status,
41654          message: 'Invalid HLS key at URL: ' + request.uri,
41655          code: REQUEST_ERRORS.FAILURE,
41656          xhr: request
41657        }, segment);
41658      }
41659
41660      var view = new DataView(response);
41661      segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
41662      return finishProcessingFn(null, segment);
41663    };
41664  };
41665  /**
41666   * Handle init-segment responses
41667   *
41668   * @param {Object} segment - a simplified copy of the segmentInfo object
41669   *                           from SegmentLoader
41670   * @param {Function} finishProcessingFn - a callback to execute to continue processing
41671   *                                        this request
41672   */
41673
41674
41675  var handleInitSegmentResponse = function handleInitSegmentResponse(_ref) {
41676    var segment = _ref.segment,
41677        finishProcessingFn = _ref.finishProcessingFn;
41678    return function (error, request) {
41679      var response = request.response;
41680      var errorObj = handleErrors(error, request);
41681
41682      if (errorObj) {
41683        return finishProcessingFn(errorObj, segment);
41684      } // stop processing if received empty content
41685
41686
41687      if (response.byteLength === 0) {
41688        return finishProcessingFn({
41689          status: request.status,
41690          message: 'Empty HLS segment content at URL: ' + request.uri,
41691          code: REQUEST_ERRORS.FAILURE,
41692          xhr: request
41693        }, segment);
41694      }
41695
41696      segment.map.bytes = new Uint8Array(request.response);
41697      var type = containers_1(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
41698      // only know how to parse mp4 init segments at the moment
41699
41700      if (type !== 'mp4') {
41701        return finishProcessingFn({
41702          status: request.status,
41703          message: "Found unsupported " + (type || 'unknown') + " container for initialization segment at URL: " + request.uri,
41704          code: REQUEST_ERRORS.FAILURE,
41705          internal: true,
41706          xhr: request
41707        }, segment);
41708      }
41709
41710      var tracks = probe$2.tracks(segment.map.bytes);
41711      tracks.forEach(function (track) {
41712        segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
41713
41714        if (segment.map.tracks[track.type]) {
41715          return;
41716        }
41717
41718        segment.map.tracks[track.type] = track;
41719
41720        if (track.id && track.timescale) {
41721          segment.map.timescales = segment.map.timescales || {};
41722          segment.map.timescales[track.id] = track.timescale;
41723        }
41724      });
41725      return finishProcessingFn(null, segment);
41726    };
41727  };
41728  /**
41729   * Response handler for segment-requests being sure to set the correct
41730   * property depending on whether the segment is encryped or not
41731   * Also records and keeps track of stats that are used for ABR purposes
41732   *
41733   * @param {Object} segment - a simplified copy of the segmentInfo object
41734   *                           from SegmentLoader
41735   * @param {Function} finishProcessingFn - a callback to execute to continue processing
41736   *                                        this request
41737   */
41738
41739
41740  var handleSegmentResponse = function handleSegmentResponse(_ref2) {
41741    var segment = _ref2.segment,
41742        finishProcessingFn = _ref2.finishProcessingFn,
41743        responseType = _ref2.responseType;
41744    return function (error, request) {
41745      var response = request.response;
41746      var errorObj = handleErrors(error, request);
41747
41748      if (errorObj) {
41749        return finishProcessingFn(errorObj, segment);
41750      }
41751
41752      var newBytes = // although responseText "should" exist, this guard serves to prevent an error being
41753      // thrown for two primary cases:
41754      // 1. the mime type override stops working, or is not implemented for a specific
41755      //    browser
41756      // 2. when using mock XHR libraries like sinon that do not allow the override behavior
41757      responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0)); // stop processing if received empty content
41758
41759      if (response.byteLength === 0) {
41760        return finishProcessingFn({
41761          status: request.status,
41762          message: 'Empty HLS segment content at URL: ' + request.uri,
41763          code: REQUEST_ERRORS.FAILURE,
41764          xhr: request
41765        }, segment);
41766      }
41767
41768      segment.stats = getRequestStats(request);
41769
41770      if (segment.key) {
41771        segment.encryptedBytes = new Uint8Array(newBytes);
41772      } else {
41773        segment.bytes = new Uint8Array(newBytes);
41774      }
41775
41776      return finishProcessingFn(null, segment);
41777    };
41778  };
41779
41780  var transmuxAndNotify = function transmuxAndNotify(_ref3) {
41781    var segment = _ref3.segment,
41782        bytes = _ref3.bytes,
41783        isPartial = _ref3.isPartial,
41784        trackInfoFn = _ref3.trackInfoFn,
41785        timingInfoFn = _ref3.timingInfoFn,
41786        videoSegmentTimingInfoFn = _ref3.videoSegmentTimingInfoFn,
41787        id3Fn = _ref3.id3Fn,
41788        captionsFn = _ref3.captionsFn,
41789        dataFn = _ref3.dataFn,
41790        doneFn = _ref3.doneFn;
41791    var fmp4Tracks = segment.map && segment.map.tracks || {};
41792    var isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
41793    // One reason for this is that in the case of full segments, we want to trust start
41794    // times from the probe, rather than the transmuxer.
41795
41796    var audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
41797    var audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
41798    var videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
41799    var videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end'); // Check to see if we are appending a full segment.
41800
41801    if (!isPartial && !segment.lastReachedChar) {
41802      // In the full segment transmuxer, we don't yet have the ability to extract a "proper"
41803      // start time. Meaning cached frame data may corrupt our notion of where this segment
41804      // really starts. To get around this, full segment appends should probe for the info
41805      // needed.
41806      var probeResult = probeTsSegment(bytes, segment.baseStartTime);
41807
41808      if (probeResult) {
41809        trackInfoFn(segment, {
41810          hasAudio: probeResult.hasAudio,
41811          hasVideo: probeResult.hasVideo,
41812          isMuxed: isMuxed
41813        });
41814        trackInfoFn = null;
41815
41816        if (probeResult.hasAudio && !isMuxed) {
41817          audioStartFn(probeResult.audioStart);
41818        }
41819
41820        if (probeResult.hasVideo) {
41821          videoStartFn(probeResult.videoStart);
41822        }
41823
41824        audioStartFn = null;
41825        videoStartFn = null;
41826      }
41827    }
41828
41829    transmux({
41830      bytes: bytes,
41831      transmuxer: segment.transmuxer,
41832      audioAppendStart: segment.audioAppendStart,
41833      gopsToAlignWith: segment.gopsToAlignWith,
41834      isPartial: isPartial,
41835      remux: isMuxed,
41836      onData: function onData(result) {
41837        result.type = result.type === 'combined' ? 'video' : result.type;
41838        dataFn(segment, result);
41839      },
41840      onTrackInfo: function onTrackInfo(trackInfo) {
41841        if (trackInfoFn) {
41842          if (isMuxed) {
41843            trackInfo.isMuxed = true;
41844          }
41845
41846          trackInfoFn(segment, trackInfo);
41847        }
41848      },
41849      onAudioTimingInfo: function onAudioTimingInfo(audioTimingInfo) {
41850        // we only want the first start value we encounter
41851        if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
41852          audioStartFn(audioTimingInfo.start);
41853          audioStartFn = null;
41854        } // we want to continually update the end time
41855
41856
41857        if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
41858          audioEndFn(audioTimingInfo.end);
41859        }
41860      },
41861      onVideoTimingInfo: function onVideoTimingInfo(videoTimingInfo) {
41862        // we only want the first start value we encounter
41863        if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
41864          videoStartFn(videoTimingInfo.start);
41865          videoStartFn = null;
41866        } // we want to continually update the end time
41867
41868
41869        if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
41870          videoEndFn(videoTimingInfo.end);
41871        }
41872      },
41873      onVideoSegmentTimingInfo: function onVideoSegmentTimingInfo(videoSegmentTimingInfo) {
41874        videoSegmentTimingInfoFn(videoSegmentTimingInfo);
41875      },
41876      onId3: function onId3(id3Frames, dispatchType) {
41877        id3Fn(segment, id3Frames, dispatchType);
41878      },
41879      onCaptions: function onCaptions(captions) {
41880        captionsFn(segment, [captions]);
41881      },
41882      onDone: function onDone(result) {
41883        // To handle partial appends, there won't be a done function passed in (since
41884        // there's still, potentially, more segment to process), so there's nothing to do.
41885        if (!doneFn || isPartial) {
41886          return;
41887        }
41888
41889        result.type = result.type === 'combined' ? 'video' : result.type;
41890        doneFn(null, segment, result);
41891      }
41892    });
41893  };
41894
41895  var handleSegmentBytes = function handleSegmentBytes(_ref4) {
41896    var segment = _ref4.segment,
41897        bytes = _ref4.bytes,
41898        isPartial = _ref4.isPartial,
41899        trackInfoFn = _ref4.trackInfoFn,
41900        timingInfoFn = _ref4.timingInfoFn,
41901        videoSegmentTimingInfoFn = _ref4.videoSegmentTimingInfoFn,
41902        id3Fn = _ref4.id3Fn,
41903        captionsFn = _ref4.captionsFn,
41904        dataFn = _ref4.dataFn,
41905        doneFn = _ref4.doneFn;
41906    var bytesAsUint8Array = new Uint8Array(bytes); // TODO:
41907    // We should have a handler that fetches the number of bytes required
41908    // to check if something is fmp4. This will allow us to save bandwidth
41909    // because we can only blacklist a playlist and abort requests
41910    // by codec after trackinfo triggers.
41911
41912    if (containers_5(bytesAsUint8Array)) {
41913      segment.isFmp4 = true;
41914      var tracks = segment.map.tracks;
41915      var trackInfo = {
41916        isFmp4: true,
41917        hasVideo: !!tracks.video,
41918        hasAudio: !!tracks.audio
41919      }; // if we have a audio track, with a codec that is not set to
41920      // encrypted audio
41921
41922      if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
41923        trackInfo.audioCodec = tracks.audio.codec;
41924      } // if we have a video track, with a codec that is not set to
41925      // encrypted video
41926
41927
41928      if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
41929        trackInfo.videoCodec = tracks.video.codec;
41930      }
41931
41932      if (tracks.video && tracks.audio) {
41933        trackInfo.isMuxed = true;
41934      } // since we don't support appending fmp4 data on progress, we know 
41934we have the full
41935      // segment here
41936
41937
41938      trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
41939      // time. The end time can be roughly calculated by the receiver using the duration.
41940      //
41941      // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
41942      // that is the true start of the segment (where the playback engine should begin
41943      // decoding).
41944
41945      var timingInfo = probe$2.startTime(segment.map.timescales, bytesAsUint8Array);
41946
41947      if (trackInfo.hasAudio && !trackInfo.isMuxed) {
41948        timingInfoFn(segment, 'audio', 'start', timingInfo);
41949      }
41950
41951      if (trackInfo.hasVideo) {
41952        timingInfoFn(segment, 'video', 'start', timingInfo);
41953      }
41954
41955      var finishLoading = function finishLoading(captions) {
41956        // if the track still has audio at this point it is only possible
41957        // for it to be audio only. See `tracks.video && tracks.audio` if statement
41958        // above.
41959        // we make sure to use segment.bytes here as that
41960        dataFn(segment, {
41961          data: bytes,
41962          type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
41963        });
41964
41965        if (captions && captions.length) {
41966          captionsFn(segment, captions);
41967        }
41968
41969        doneFn(null, segment, {});
41970      }; // Run through the CaptionParser in case there are captions.
41971      // Initialize CaptionParser if it hasn't been yet
41972
41973
41974      if (!tracks.video || !bytes.byteLength || !segment.transmuxer) {
41975        finishLoading();
41976        return;
41977      }
41978
41979      var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
41980      var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
41981
41982      var listenForCaptions = function listenForCaptions(event) {
41983        if (event.data.action !== 'mp4Captions') {
41984          return;
41985        }
41986
41987        segment.transmuxer.removeEventListener('message', listenForCaptions);
41988        var data = event.data.data; // transfer ownership of bytes back to us.
41989
41990        segment.bytes = bytes = new Uint8Array(data, data.byteOffset || 0, data.byteLength);
41991        finishLoading(event.data.captions);
41992      };
41993
41994      segment.transmuxer.addEventListener('message', listenForCaptions); // transfer ownership of bytes to worker.
41995
41996      segment.transmuxer.postMessage({
41997        action: 'pushMp4Captions',
41998        timescales: segment.map.timescales,
41999        trackIds: [tracks.video.id],
42000        data: buffer,
42001        byteOffset: byteOffset,
42002        byteLength: bytes.byteLength
42003      }, [buffer]);
42004      return;
42005    } // VTT or other segments that don't need processing
42006
42007
42008    if (!segment.transmuxer) {
42009      doneFn(null, segment, {});
42010      return;
42011    }
42012
42013    if (typeof segment.container === 'undefined') {
42014      segment.container = containers_1(bytesAsUint8Array);
42015    }
42016
42017    if (segment.container !== 'ts' && segment.container !== 'aac') {
42018      trackInfoFn(segment, {
42019        hasAudio: false,
42020        hasVideo: false
42021      });
42022      doneFn(null, segment, {});
42023      return;
42024    } // ts or aac
42025
42026
42027    transmuxAndNotify({
42028      segment: segment,
42029      bytes: bytes,
42030      isPartial: isPartial,
42031      trackInfoFn: trackInfoFn,
42032      timingInfoFn: timingInfoFn,
42033      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42034      id3Fn: id3Fn,
42035      captionsFn: captionsFn,
42036      dataFn: dataFn,
42037      doneFn: doneFn
42038    });
42039  };
42040  /**
42041   * Decrypt the segment via the decryption web worker
42042   *
42043   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
42044   *                                       routines
42045   * @param {Object} segment - a simplified copy of the segmentInfo object
42046   *                           from SegmentLoader
42047   * @param {Function} trackInfoFn - a callback that receives track info
42048   * @param {Function} dataFn - a callback that is executed when segment bytes are available
42049   *                            and ready to use
42050   * @param {Function} doneFn - a callback that is executed after decryption has completed
42051   */
42052
42053
42054  var decryptSegment = function decryptSegment(_ref5) {
42055    var decryptionWorker = _ref5.decryptionWorker,
42056        segment = _ref5.segment,
42057        trackInfoFn = _ref5.trackInfoFn,
42058        timingInfoFn = _ref5.timingInfoFn,
42059        videoSegmentTimingInfoFn = _ref5.videoSegmentTimingInfoFn,
42060        id3Fn = _ref5.id3Fn,
42061        captionsFn = _ref5.captionsFn,
42062        dataFn = _ref5.dataFn,
42063        doneFn = _ref5.doneFn;
42064
42065    var decryptionHandler = function decryptionHandler(event) {
42066      if (event.data.source === segment.requestId) {
42067        decryptionWorker.removeEventListener('message', decryptionHandler);
42068        var decrypted = event.data.decrypted;
42069        segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
42070        handleSegmentBytes({
42071          segment: segment,
42072          bytes: segment.bytes,
42073          isPartial: false,
42074          trackInfoFn: trackInfoFn,
42075          timingInfoFn: timingInfoFn,
42076          videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42077          id3Fn: id3Fn,
42078          captionsFn: captionsFn,
42079          dataFn: dataFn,
42080          doneFn: doneFn
42081        });
42082      }
42083    };
42084
42085    decryptionWorker.addEventListener('message', decryptionHandler);
42086    var keyBytes;
42087
42088    if (segment.key.bytes.slice) {
42089      keyBytes = segment.key.bytes.slice();
42090    } else {
42091      keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes));
42092    } // this is an encrypted segment
42093    // incrementally decrypt the segment
42094
42095
42096    decryptionWorker.postMessage(createTransferableMessage({
42097      source: segment.requestId,
42098      encrypted: segment.encryptedBytes,
42099      key: keyBytes,
42100      iv: segment.key.iv
42101    }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
42102  };
42103  /**
42104   * This function waits for all XHRs to finish (with either success or failure)
42105   * before continueing processing via it's callback. The function gathers errors
42106   * from each request into a single errors array so that the error status for
42107   * each request can be examined later.
42108   *
42109   * @param {Object} activeXhrs - an object that tracks all XHR requests
42110   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
42111   *                                       routines
42112   * @param {Function} trackInfoFn - a callback that receives track info
42113   * @param {Function} timingInfoFn - a callback that receives timing info
42114   * @param {Function} id3Fn - a callback that receives ID3 metadata
42115   * @param {Function} captionsFn - a callback that receives captions
42116   * @param {Function} dataFn - a callback that is executed when segment bytes are available
42117   *                            and ready to use
42118   * @param {Function} doneFn - a callback that is executed after all resources have been
42119   *                            downloaded and any decryption completed
42120   */
42121
42122
42123  var waitForCompletion = function waitForCompletion(_ref6) {
42124    var activeXhrs = _ref6.activeXhrs,
42125        decryptionWorker = _ref6.decryptionWorker,
42126        trackInfoFn = _ref6.trackInfoFn,
42127        timingInfoFn = _ref6.timingInfoFn,
42128        videoSegmentTimingInfoFn = _ref6.videoSegmentTimingInfoFn,
42129        id3Fn = _ref6.id3Fn,
42130        captionsFn = _ref6.captionsFn,
42131        dataFn = _ref6.dataFn,
42132        doneFn = _ref6.doneFn;
42133    var count = 0;
42134    var didError = false;
42135    return function (error, segment) {
42136      if (didError) {
42137        return;
42138      }
42139
42140      if (error) {
42141        didError = true; // If there are errors, we have to abort any outstanding requests
42142
42143        abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
42144        // handle the aborted events from those requests, there are some cases where we may
42145        // never get an aborted event. For instance, if the network connection is lost and
42146        // there were two requests, the first may have triggered an error immediately, while
42147        // the second request remains unsent. In that case, the aborted algorithm will not
42148        // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
42149        //
42150        // We also can't rely on the ready state of the XHR, since the request that
42151        // triggered the connection error may also show as a ready state of 0 (unsent).
42152        // Therefore, we have to finish this group of requests immediately after the first
42153        // seen error.
42154
42155        return doneFn(error, segment);
42156      }
42157
42158      count += 1;
42159
42160      if (count === activeXhrs.length) {
42161        // Keep track of when *all* of the requests have completed
42162        segment.endOfAllRequests = Date.now();
42163
42164        if (segment.encryptedBytes) {
42165          return decryptSegment({
42166            decryptionWorker: decryptionWorker,
42167            segment: segment,
42168            trackInfoFn: trackInfoFn,
42169            timingInfoFn: timingInfoFn,
42170            videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42171            id3Fn: id3Fn,
42172            captionsFn: captionsFn,
42173            dataFn: dataFn,
42174            doneFn: doneFn
42175          });
42176        } // Otherwise, everything is ready just continue
42177
42178
42179        handleSegmentBytes({
42180          segment: segment,
42181          bytes: segment.bytes,
42182          isPartial: false,
42183          trackInfoFn: trackInfoFn,
42184          timingInfoFn: timingInfoFn,
42185          videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42186          id3Fn: id3Fn,
42187          captionsFn: captionsFn,
42188          dataFn: dataFn,
42189          doneFn: doneFn
42190        });
42191      }
42192    };
42193  };
42194  /**
42195   * Calls the abort callback if any request within the batch was aborted. Will only call
42196   * the callback once per batch of requests, even if multiple were aborted.
42197   *
42198   * @param {Object} loadendState - state to check to see if the abort function was called
42199   * @param {Function} abortFn - callback to call for abort
42200   */
42201
42202
42203  var handleLoadEnd = function handleLoadEnd(_ref7) {
42204    var loadendState = _ref7.loadendState,
42205        abortFn = _ref7.abortFn;
42206    return function (event) {
42207      var request = event.target;
42208
42209      if (request.aborted && abortFn && !loadendState.calledAbortFn) {
42210        abortFn();
42211        loadendState.calledAbortFn = true;
42212      }
42213    };
42214  };
42215  /**
42216   * Simple progress event callback handler that gathers some stats before
42217   * executing a provided callback with the `segment` object
42218   *
42219   * @param {Object} segment - a simplified copy of the segmentInfo object
42220   *                           from SegmentLoader
42221   * @param {Function} progressFn - a callback that is executed each time a progress event
42222   *                                is received
42223   * @param {Function} trackInfoFn - a callback that receives track info
42224   * @param {Function} dataFn - a callback that is executed when segment bytes are available
42225   *                            and ready to use
42226   * @param {Event} event - the progress event object from XMLHttpRequest
42227   */
42228
42229
42230  var handleProgress = function handleProgress(_ref8) {
42231    var segment = _ref8.segment,
42232        progressFn = _ref8.progressFn,
42233        trackInfoFn = _ref8.trackInfoFn,
42234        timingInfoFn = _ref8.timingInfoFn,
42235        videoSegmentTimingInfoFn = _ref8.videoSegmentTimingInfoFn,
42236        id3Fn = _ref8.id3Fn,
42237        captionsFn = _ref8.captionsFn,
42238        dataFn = _ref8.dataFn,
42239        handlePartialData = _ref8.handlePartialData;
42240    return function (event) {
42241      var request = event.target;
42242
42243      if (request.aborted) {
42244        return;
42245      } // don't support encrypted segments or fmp4 for now
42246
42247
42248      if (handlePartialData && !segment.key && // although responseText "should" exist, this guard serves to prevent an error being
42249      // thrown on the next check for two primary cases:
42250      // 1. the mime type override stops working, or is not implemented for a specific
42251      //    browser
42252      // 2. when using mock XHR libraries like sinon that do not allow the override behavior
42253      request.responseText && // in order to determine if it's an fmp4 we need at least 8 bytes
42254      request.responseText.length >= 8) {
42255        var newBytes = stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0
42255));
42256
42257        if (segment.lastReachedChar || !containers_5(new Uint8Array(newBytes))) {
42258          segment.lastReachedChar = request.responseText.length;
42259          handleSegmentBytes({
42260            segment: segment,
42261            bytes: newBytes,
42262            isPartial: true,
42263            trackInfoFn: trackInfoFn,
42264            timingInfoFn: timingInfoFn,
42265            videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42266            id3Fn: id3Fn,
42267            captionsFn: captionsFn,
42268            dataFn: dataFn
42269          });
42270        }
42271      }
42272
42273      segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
42274
42275      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
42276        segment.stats.firstBytesReceivedAt = Date.now();
42277      }
42278
42279      return progressFn(event, segment);
42280    };
42281  };
42282  /**
42283   * Load all resources and does any processing necessary for a media-segment
42284   *
42285   * Features:
42286   *   decrypts the media-segment if it has a key uri and an iv
42287   *   aborts *all* requests if *any* one request fails
42288   *
42289   * The segment object, at minimum, has the following format:
42290   * {
42291   *   resolvedUri: String,
42292   *   [transmuxer]: Object,
42293   *   [byterange]: {
42294   *     offset: Number,
42295   *     length: Number
42296   *   },
42297   *   [key]: {
42298   *     resolvedUri: String
42299   *     [byterange]: {
42300   *       offset: Number,
42301   *       length: Number
42302   *     },
42303   *     iv: {
42304   *       bytes: Uint32Array
42305   *     }
42306   *   },
42307   *   [map]: {
42308   *     resolvedUri: String,
42309   *     [byterange]: {
42310   *       offset: Number,
42311   *       length: Number
42312   *     },
42313   *     [bytes]: Uint8Array
42314   *   }
42315   * }
42316   * ...where [name] denotes optional properties
42317   *
42318   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
42319   * @param {Object} xhrOptions - the base options to provide to all xhr requests
42320   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
42321   *                                       decryption routines
42322   * @param {Object} segment - a simplified copy of the segmentInfo object
42323   *                           from SegmentLoader
42324   * @param {Function} abortFn - a callback called (only once) if any piece of a request was
42325   *                             aborted
42326   * @param {Function} progressFn - a callback that receives progress events from the main
42327   *                                segment's xhr request
42328   * @param {Function} trackInfoFn - a callback that receives track info
42329   * @param {Function} id3Fn - a callback that receives ID3 metadata
42330   * @param {Function} captionsFn - a callback that receives captions
42331   * @param {Function} dataFn - a callback that receives data from the main segment's xhr
42332   *                            request, transmuxed if needed
42333   * @param {Function} doneFn - a callback that is executed only once all requests have
42334   *                            succeeded or failed
42335   * @return {Function} a function that, when invoked, immediately aborts all
42336   *                     outstanding requests
42337   */
42338
42339
42340  var mediaSegmentRequest = function mediaSegmentRequest(_ref9) {
42341    var xhr = _ref9.xhr,
42342        xhrOptions = _ref9.xhrOptions,
42343        decryptionWorker = _ref9.decryptionWorker,
42344        segment = _ref9.segment,
42345        abortFn = _ref9.abortFn,
42346        progressFn = _ref9.progressFn,
42347        trackInfoFn = _ref9.trackInfoFn,
42348        timingInfoFn = _ref9.timingInfoFn,
42349        videoSegmentTimingInfoFn = _ref9.videoSegmentTimingInfoFn,
42350        id3Fn = _ref9.id3Fn,
42351        captionsFn = _ref9.captionsFn,
42352        dataFn = _ref9.dataFn,
42353        doneFn = _ref9.doneFn,
42354        handlePartialData = _ref9.handlePartialData;
42355    var activeXhrs = [];
42356    var finishProcessingFn = waitForCompletion({
42357      activeXhrs: activeXhrs,
42358      decryptionWorker: decryptionWorker,
42359      trackInfoFn: trackInfoFn,
42360      timingInfoFn: timingInfoFn,
42361      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42362      id3Fn: id3Fn,
42363      captionsFn: captionsFn,
42364      dataFn: dataFn,
42365      doneFn: doneFn
42366    }); // optionally, request the decryption key
42367
42368    if (segment.key && !segment.key.bytes) {
42369      var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
42370        uri: segment.key.resolvedUri,
42371        responseType: 'arraybuffer'
42372      });
42373      var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
42374      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
42375      activeXhrs.push(keyXhr);
42376    } // optionally, request the associated media init segment
42377
42378
42379    if (segment.map && !segment.map.bytes) {
42380      var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
42381        uri: segment.map.resolvedUri,
42382        responseType: 'arraybuffer',
42383        headers: segmentXhrHeaders(segment.map)
42384      });
42385      var initSegmentRequestCallback = handleInitSegmentResponse({
42386        segment: segment,
42387        finishProcessingFn: finishProcessingFn
42388      });
42389      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
42390      activeXhrs.push(initSegmentXhr);
42391    }
42392
42393    var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
42394      uri: segment.resolvedUri,
42395      responseType: 'arraybuffer',
42396      headers: segmentXhrHeaders(segment)
42397    });
42398
42399    if (handlePartialData) {
42400      // setting to text is required for partial responses
42401      // conversion to ArrayBuffer happens later
42402      segmentRequestOptions.responseType = 'text';
42403
42404      segmentRequestOptions.beforeSend = function (xhrObject) {
42405        // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
42406        // makes the browser pass through the "text" unparsed
42407        xhrObject.overrideMimeType('text/plain;
vendor: 10,915 bytes, lines 42407-42762
42407 charset=x-user-defined');
42408      };
42409    }
42410
42411    var segmentRequestCallback = handleSegmentResponse({
42412      segment: segment,
42413      finishProcessingFn: finishProcessingFn,
42414      responseType: segmentRequestOptions.responseType
42415    });
42416    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
42417    segmentXhr.addEventListener('progress', handleProgress({
42418      segment: segment,
42419      progressFn: progressFn,
42420      trackInfoFn: trackInfoFn,
42421      timingInfoFn: timingInfoFn,
42422      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
42423      id3Fn: id3Fn,
42424      captionsFn: captionsFn,
42425      dataFn: dataFn,
42426      handlePartialData: handlePartialData
42427    }));
42428    activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
42429    // multiple times, provide a shared state object
42430
42431    var loadendState = {};
42432    activeXhrs.forEach(function (activeXhr) {
42433      activeXhr.addEventListener('loadend', handleLoadEnd({
42434        loadendState: loadendState,
42435        abortFn: abortFn
42436      }));
42437    });
42438    return function () {
42439      return abortAll(activeXhrs);
42440    };
42441  };
42442
42443  var win = typeof window !== 'undefined' ? window : {},
42444      TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
42445      SCRIPT_TYPE = 'application/javascript',
42446      BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
42447      URL = win.URL || win.webkitURL || URL && URL.msURL,
42448      Worker = win.Worker;
42449  /**
42450   * Returns a wrapper around Web Worker code that is constructible.
42451   *
42452   * @function shimWorker
42453   *
42454   * @param { String }    filename    The name of the file
42455   * @param { Function }  fn          Function wrapping the code of the worker
42456   */
42457
42458  function shimWorker(filename, fn) {
42459    return function ShimWorker(forceFallback) {
42460      var o = this;
42461
42462      if (!fn) {
42463        return new Worker(filename);
42464      } else if (Worker && !forceFallback) {
42465        // Convert the function's inner code to a string to construct the worker
42466        var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
42467            objURL = createSourceObject(source);
42468        this[TARGET] = new Worker(objURL);
42469        wrapTerminate(this[TARGET], objURL);
42470        return this[TARGET];
42471      } else {
42472        var selfShim = {
42473          postMessage: function postMessage(m) {
42474            if (o.onmessage) {
42475              setTimeout(function () {
42476                o.onmessage({
42477                  data: m,
42478                  target: selfShim
42479                });
42480              });
42481            }
42482          }
42483        };
42484        fn.call(selfShim);
42485
42486        this.postMessage = function (m) {
42487          setTimeout(function () {
42488            selfShim.onmessage({
42489              data: m,
42490              target: o
42491            });
42492          });
42493        };
42494
42495        this.isThisThread = true;
42496      }
42497    };
42498  } // Test Worker capabilities
42499
42500
42501  if (Worker) {
42502    var testWorker,
42503        objURL = createSourceObject('self.onmessage = function () {}'),
42504        testArray = new Uint8Array(1);
42505
42506    try {
42507      testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
42508
42509      testWorker.postMessage(testArray, [testArray.buffer]);
42510    } catch (e) {
42511      Worker = null;
42512    } finally {
42513      URL.revokeObjectURL(objURL);
42514
42515      if (testWorker) {
42516        testWorker.terminate();
42517      }
42518    }
42519  }
42520
42521  function createSourceObject(str) {
42522    try {
42523      return URL.createObjectURL(new Blob([str], {
42524        type: SCRIPT_TYPE
42525      }));
42526    } catch (e) {
42527      var blob = new BlobBuilder();
42528      blob.append(str);
42529      return URL.createObjectURL(blob.getBlob(type));
42530    }
42531  }
42532
42533  function wrapTerminate(worker, objURL) {
42534    if (!worker || !objURL) return;
42535    var term = worker.terminate;
42536    worker.objURL = objURL;
42537
42538    worker.terminate = function () {
42539      if (worker.objURL) URL.revokeObjectURL(worker.objURL);
42540      term.call(worker);
42541    };
42542  }
42543
42544  var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document) {
42545    var self = this;
42546    /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */
42547
42548    var transmuxerWorker = function () {
42549      /**
42550       * mux.js
42551       *
42552       * Copyright (c) Brightcove
42553       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
42554       *
42555       * A lightweight readable stream implemention that handles event dispatching.
42556       * Objects that inherit from streams should call init in their constructors.
42557       */
42558      var Stream = function Stream() {
42559        this.init = function () {
42560          var listeners = {};
42561          /**
42562           * Add a listener for a specified event type.
42563           * @param type {string} the event name
42564           * @param listener {function} the callback to be invoked when an event of
42565           * the specified type occurs
42566           */
42567
42568          this.on = function (type, listener) {
42569            if (!listeners[type]) {
42570              listeners[type] = [];
42571            }
42572
42573            listeners[type] = listeners[type].concat(listener);
42574          };
42575          /**
42576           * Remove a listener for a specified event type.
42577           * @param type {string} the event name
42578           * @param listener {function} a function previously registered for this
42579           * type of event through `on`
42580           */
42581
42582
42583          this.off = function (type, listener) {
42584            var index;
42585
42586            if (!listeners[type]) {
42587              return false;
42588            }
42589
42590            index = listeners[type].indexOf(listener);
42591            listeners[type] = listeners[type].slice();
42592            listeners[type].splice(index, 1);
42593            return index > -1;
42594          };
42595          /**
42596           * Trigger an event of the specified type on this stream. Any additional
42597           * arguments to this function are passed as parameters to event listeners.
42598           * @param type {string} the event name
42599           */
42600
42601
42602          this.trigger = function (type) {
42603            var callbacks, i, length, args;
42604            callbacks = listeners[type];
42605
42606            if (!callbacks) {
42607              return;
42608            } // Slicing the arguments on every invocation of this method
42609            // can add a significant amount of overhead. Avoid the
42610            // intermediate object creation for the common case of a
42611            // single callback argument
42612
42613
42614            if (arguments.length === 2) {
42615              length = callbacks.length;
42616
42617              for (i = 0; i < length; ++i) {
42618                callbacks[i].call(this, arguments[1]);
42619              }
42620            } else {
42621              args = [];
42622              i = arguments.length;
42623
42624              for (i = 1; i < arguments.length; ++i) {
42625                args.push(arguments[i]);
42626              }
42627
42628              length = callbacks.length;
42629
42630              for (i = 0; i < length; ++i) {
42631                callbacks[i].apply(this, args);
42632              }
42633            }
42634          };
42635          /**
42636           * Destroys the stream and cleans up.
42637           */
42638
42639
42640          this.dispose = function () {
42641            listeners = {};
42642          };
42643        };
42644      };
42645      /**
42646       * Forwards all `data` events on this stream to the destination stream. The
42647       * destination stream should provide a method `push` to receive the data
42648       * events as they arrive.
42649       * @param destination {stream} the stream that will receive all `data` events
42650       * @param autoFlush {boolean} if false, we will not call `flush` on the destination
42651       *                            when the current stream emits a 'done' event
42652       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
42653       */
42654
42655
42656      Stream.prototype.pipe = function (destination) {
42657        this.on('data', function (data) {
42658          destination.push(data);
42659        });
42660        this.on('done', function (flushSource) {
42661          destination.flush(flushSource);
42662        });
42663        this.on('partialdone', function (flushSource) {
42664          destination.partialFlush(flushSource);
42665        });
42666        this.on('endedtimeline', function (flushSource) {
42667          destination.endTimeline(flushSource);
42668        });
42669        this.on('reset', function (flushSource) {
42670          destination.reset(flushSource);
42671        });
42672        return destination;
42673      }; // Default stream functions that are expected to be overridden to perform
42674      // actual work. These are provided by the prototype as a sort of no-op
42675      // implementation so that we don't have to check for their existence in the
42676      // `pipe` function above.
42677
42678
42679      Stream.prototype.push = function (data) {
42680        this.trigger('data', data);
42681      };
42682
42683      Stream.prototype.flush = function (flushSource) {
42684        this.trigger('done', flushSource);
42685      };
42686
42687      Stream.prototype.partialFlush = function (flushSource) {
42688        this.trigger('partialdone', flushSource);
42689      };
42690
42691      Stream.prototype.endTimeline = function (flushSource) {
42692        this.trigger('endedtimeline', flushSource);
42693      };
42694
42695      Stream.prototype.reset = function (flushSource) {
42696        this.trigger('reset', flushSource);
42697      };
42698
42699      var stream = Stream;
42700      /**
42701       * mux.js
42702       *
42703       * Copyright (c) Brightcove
42704       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
42705       *
42706       * Functions that generate fragmented MP4s suitable for use with Media
42707       * Source Extensions.
42708       */
42709
42710      var UINT32_MAX = Math.pow(2, 32) - 1;
42711      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; // pre-calculate constants
42712
42713      (function () {
42714        var i;
42715        types = {
42716          avc1: [],
42717          // codingname
42718          avcC: [],
42719          btrt: [],
42720          dinf: [],
42721          dref: [],
42722          esds: [],
42723          ftyp: [],
42724          hdlr: [],
42725          mdat: [],
42726          mdhd: [],
42727          mdia: [],
42728          mfhd: [],
42729          minf: [],
42730          moof: [],
42731          moov: [],
42732          mp4a: [],
42733          // codingname
42734          mvex: [],
42735          mvhd: [],
42736          pasp: [],
42737          sdtp: [],
42738          smhd: [],
42739          stbl: [],
42740          stco: [],
42741          stsc: [],
42742          stsd: [],
42743          stsz: [],
42744          stts: [],
42745          styp: [],
42746          tfdt: [],
42747          tfhd: [],
42748          traf: [],
42749          trak: [],
42750          trun: [],
42751          trex: [],
42752          tkhd: [],
42753          vmhd: []
42754        }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
42755        // don't throw an error
42756
42757        if (typeof Uint8Array === 'undefined') {
42758          return;
42759        }
42760
42761        for (i in types) {
42762          if (types.hasOwnProperty(i)) {
vendor: 51,974 bytes, lines 42763-44119
42763            types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
42764          }
42765        }
42766
42767        MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
42768        AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
42769        MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
42770        VIDEO_HDLR = new Uint8Array([0x00, // version 0
42771        0x00, 0x00, 0x00, // flags
42772        0x00, 0x00, 0x00, 0x00, // pre_defined
42773        0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
42774        0x00, 0x00, 0x00, 0x00, // reserved
42775        0x00, 0x00, 0x00, 0x00, // reserved
42776        0x00, 0x00, 0x00, 0x00, // reserved
42777        0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
42778        ]);
42779        AUDIO_HDLR = new Uint8Array([0x00, // version 0
42780        0x00, 0x00, 0x00, // flags
42781        0x00, 0x00, 0x00, 0x00, // pre_defined
42782        0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
42783        0x00, 0x00, 0x00, 0x00, // reserved
42784        0x00, 0x00, 0x00, 0x00, // reserved
42785        0x00, 0x00, 0x00, 0x00, // reserved
42786        0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
42787        ]);
42788        HDLR_TYPES = {
42789          video: VIDEO_HDLR,
42790          audio: AUDIO_HDLR
42791        };
42792        DREF = new Uint8Array([0x00, // version 0
42793        0x00, 0x00, 0x00, // flags
42794        0x00, 0x00, 0x00, 0x01, // entry_count
42795        0x00, 0x00, 0x00, 0x0c, // entry_size
42796        0x75, 0x72, 0x6c, 0x20, // 'url' type
42797        0x00, // version 0
42798        0x00, 0x00, 0x01 // entry_flags
42799        ]);
42800        SMHD = new Uint8Array([0x00, // version
42801        0x00, 0x00, 0x00, // flags
42802        0x00, 0x00, // balance, 0 means centered
42803        0x00, 0x00 // reserved
42804        ]);
42805        STCO = new Uint8Array([0x00, // version
42806        0x00, 0x00, 0x00, // flags
42807        0x00, 0x00, 0x00, 0x00 // entry_count
42808        ]);
42809        STSC = STCO;
42810        STSZ = new Uint8Array([0x00, // version
42811        0x00, 0x00, 0x00, // flags
42812        0x00, 0x00, 0x00, 0x00, // sample_size
42813        0x00, 0x00, 0x00, 0x00 // sample_count
42814        ]);
42815        STTS = STCO;
42816        VMHD = new Uint8Array([0x00, // version
42817        0x00, 0x00, 0x01, // flags
42818        0x00, 0x00, // graphicsmode
42819        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
42820        ]);
42821      })();
42822
42823      box = function box(type) {
42824        var payload = [],
42825            size = 0,
42826            i,
42827            result,
42828            view;
42829
42830        for (i = 1; i < arguments.length; i++) {
42831          payload.push(arguments[i]);
42832        }
42833
42834        i = payload.length; // calculate the total size we need to allocate
42835
42836        while (i--) {
42837          size += payload[i].byteLength;
42838        }
42839
42840        result = new Uint8Array(size + 8);
42841        view = new DataView(result.buffer, result.byteOffset, result.byteLength);
42842        view.setUint32(0, result.byteLength);
42843        result.set(type, 4); // copy the payload into the result
42844
42845        for (i = 0, size = 8; i < payload.length; i++) {
42846          result.set(payload[i], size);
42847          size += payload[i].byteLength;
42848        }
42849
42850        return result;
42851      };
42852
42853      dinf = function dinf() {
42854        return box(types.dinf, box(types.dref, DREF));
42855      };
42856
42857      esds = function esds(track) {
42858        return box(types.esds, new Uint8Array([0x00, // version
42859        0x00, 0x00, 0x00, // flags
42860        // ES_Descriptor
42861        0x03, // tag, ES_DescrTag
42862        0x19, // length
42863        0x00, 0x00, // ES_ID
42864        0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
42865        // DecoderConfigDescriptor
42866        0x04, // tag, DecoderConfigDescrTag
42867        0x11, // length
42868        0x40, // object type
42869        0x15, // streamType
42870        0x00, 0x06, 0x00, // bufferSizeDB
42871        0x00, 0x00, 0xda, 0xc0, // maxBitrate
42872        0x00, 0x00, 0xda, 0xc0, // avgBitrate
42873        // DecoderSpecificInfo
42874        0x05, // tag, DecoderSpecificInfoTag
42875        0x02, // length
42876        // ISO/IEC 14496-3, AudioSpecificConfig
42877        // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
42878        track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
42879        ]));
42880      };
42881
42882      ftyp = function ftyp() {
42883        return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
42884      };
42885
42886      hdlr = function hdlr(type) {
42887        return box(types.hdlr, HDLR_TYPES[type]);
42888      };
42889
42890      mdat = function mdat(data) {
42891        return box(types.mdat, data);
42892      };
42893
42894      mdhd = function mdhd(track) {
42895        var result = new Uint8Array([0x00, // version 0
42896        0x00, 0x00, 0x00, // flags
42897        0x00, 0x00, 0x00, 0x02, // creation_time
42898        0x00, 0x00, 0x00, 0x03, // modification_time
42899        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
42900        track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
42901        0x55, 0xc4, // 'und' language (undetermined)
42902        0x00, 0x00]); // Use the sample rate from the track metadata, when it is
42903        // defined. The sample rate can be parsed out of an ADTS header, for
42904        // instance.
42905
42906        if (track.samplerate) {
42907          result[12] = track.samplerate >>> 24 & 0xFF;
42908          result[13] = track.samplerate >>> 16 & 0xFF;
42909          result[14] = track.samplerate >>> 8 & 0xFF;
42910          result[15] = track.samplerate & 0xFF;
42911        }
42912
42913        return box(types.mdhd, result);
42914      };
42915
42916      mdia = function mdia(track) {
42917        return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
42918      };
42919
42920      mfhd = function mfhd(sequenceNumber) {
42921        return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
42922        (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
42923        ]));
42924      };
42925
42926      minf = function minf(track) {
42927        return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
42928      };
42929
42930      moof = function moof(sequenceNumber, tracks) {
42931        var trackFragments = [],
42932            i = tracks.length; // build traf boxes for each track fragment
42933
42934        while (i--) {
42935          trackFragments[i] = traf(tracks[i]);
42936        }
42937
42938        return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
42939      };
42940      /**
42941       * Returns a movie box.
42942       * @param tracks {array} the tracks associated with this movie
42943       * @see ISO/IEC 14496-12:2012(E), section 8.2.1
42944       */
42945
42946
42947      moov = function moov(tracks) {
42948        var i = tracks.length,
42949            boxes = [];
42950
42951        while (i--) {
42952          boxes[i] = trak(tracks[i]);
42953        }
42954
42955        return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
42956      };
42957
42958      mvex = function mvex(tracks) {
42959        var i = tracks.length,
42960            boxes = [];
42961
42962        while (i--) {
42963          boxes[i] = trex(tracks[i]);
42964        }
42965
42966        return box.apply(null, [types.mvex].concat(boxes));
42967      };
42968
42969      mvhd = function mvhd(duration) {
42970        var bytes = new Uint8Array([0x00, // version 0
42971        0x00, 0x00, 0x00, // flags
42972        0x00, 0x00, 0x00, 0x01, // creation_time
42973        0x00, 0x00, 0x00, 0x02, // modification_time
42974        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
42975        (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
42976        0x00, 0x01, 0x00, 0x00, // 1.0 rate
42977        0x01, 0x00, // 1.0 volume
42978        0x00, 0x00, // reserved
42979        0x00, 0x00, 0x00, 0x00, // reserved
42980        0x00, 0x00, 0x00, 0x00, // reserved
42981        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
42982        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
42983        0xff, 0xff, 0xff, 0xff // next_track_ID
42984        ]);
42985        return box(types.mvhd, bytes);
42986      };
42987
42988      sdtp = function sdtp(track) {
42989        var samples = track.samples || [],
42990            bytes = new Uint8Array(4 + samples.length),
42991            flags,
42992            i; // leave the full box header (4 bytes) all zero
42993        // write the sample table
42994
42995        for (i = 0; i < samples.length; i++) {
42996          flags = samples[i].flags;
42997          bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
42998        }
42999
43000        return box(types.sdtp, bytes);
43001      };
43002
43003      stbl = function stbl(track) {
43004        return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
43005      };
43006
43007      (function () {
43008        var videoSample, audioSample;
43009
43010        stsd = function stsd(track) {
43011          return box(types.stsd, new Uint8Array([0x00, // version 0
43012          0x00, 0x00, 0x00, // flags
43013          0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
43014        };
43015
43016        videoSample = function videoSample(track) {
43017          var sps = track.sps || [],
43018              pps = track.pps || [],
43019              sequenceParameterSets = [],
43020              pictureParameterSets = [],
43021              i,
43022              avc1Box; // assemble the SPSs
43023
43024          for (i = 0; i < sps.length; i++) {
43025            sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
43026            sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
43027
43028            sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
43029          } // assemble the PPSs
43030
43031
43032          for (i = 0; i < pps.length; i++) {
43033            pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
43034            pictureParameterSets.push(pps[i].byteLength & 0xFF);
43035            pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
43036          }
43037
43038          avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43039          0x00, 0x01, // data_reference_index
43040          0x00, 0x00, // pre_defined
43041          0x00, 0x00, // reserved
43042          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
43043          (track.width & 0xff00) >> 8, track.width & 0xff, // width
43044          (track.height & 0xff00) >> 8, track.height & 0xff, // height
43045          0x00, 0x48, 0x00, 0x00, // horizresolution
43046          0x00, 0x48, 0x00, 0x00, // vertresolution
43047          0x00, 0x00, 0x00, 0x00, // reserved
43048          0x00, 0x01, // frame_count
43049          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
43050          0x00, 0x18, // depth = 24
43051          0x11, 0x11 // pre_defined = -1
43052          ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
43053          track.profileIdc, // AVCProfileIndication
43054          track.profileCompatibility, // profile_compatibility
43055          track.levelIdc, // AVCLevelIndication
43056          0xff // lengthSizeMinusOne, hard-coded to 4 bytes
43057          ].concat([sps.length], // numOfSequenceParameterSets
43058          sequenceParameterSets, // "SPS"
43059          [pps.length], // numOfPictureParameterSets
43060          pictureParameterSets // "PPS"
43061          ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
43062          0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
43063          0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
43064          ]))];
43065
43066          if (track.sarRatio) {
43067            var hSpacing = track.sarRatio[0],
43068                vSpacing = track.sarRatio[1];
43069            avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
43070          }
43071
43072          return box.apply(null, avc1Box);
43073        };
43074
43075        audioSample = function audioSample(track) {
43076          return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
43077          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43078          0x00, 0x01, // data_reference_index
43079          // AudioSampleEntry, ISO/IEC 14496-12
43080          0x00, 0x00, 0x00, 0x00, // reserved
43081          0x00, 0x00, 0x00, 0x00, // reserved
43082          (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
43083          (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
43084          0x00, 0x00, // pre_defined
43085          0x00, 0x00, // reserved
43086          (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
43087          // MP4AudioSampleEntry, ISO/IEC 14496-14
43088          ]), esds(track));
43089        };
43090      })();
43091
43092      tkhd = function tkhd(track) {
43093        var result = new Uint8Array([0x00, // version 0
43094        0x00, 0x00, 0x07, // flags
43095        0x00, 0x00, 0x00, 0x00, // creation_time
43096        0x00, 0x00, 0x00, 0x00, // modification_time
43097        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43098        0x00, 0x00, 0x00, 0x00, // reserved
43099        (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
43100        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43101        0x00, 0x00, // layer
43102        0x00, 0x00, // alternate_group
43103        0x01, 0x00, // non-audio track volume
43104        0x00, 0x00, // reserved
43105        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
43106        (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
43107        (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
43108        ]);
43109        return box(types.tkhd, result);
43110      };
43111      /**
43112       * Generate a track fragment (traf) box. A traf box collects metadata
43113       * about tracks in a movie fragment (moof) box.
43114       */
43115
43116
43117      traf = function traf(track) {
43118        var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
43119        trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
43120        0x00, 0x00, 0x3a, // flags
43121        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43122        0x00, 0x00, 0x00, 0x01, // sample_description_index
43123        0x00, 0x00, 0x00, 0x00, // default_sample_duration
43124        0x00, 0x00, 0x00, 0x00, // default_sample_size
43125        0x00, 0x00, 0x00, 0x00 // default_sample_flags
43126        ]));
43127        upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
43128        lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
43129        trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
43130        0x00, 0x00, 0x00, // flags
43131        // baseMediaDecodeTime
43132        upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
43133        // the containing moof to the first payload byte of the associated
43134        // mdat
43135
43136        dataOffset = 32 + // tfhd
43137        20 + // tfdt
43138        8 + // traf header
43139        16 + // mfhd
43140        8 + // moof header
43141        8; // mdat header
43142        // audio tracks require less metadata
43143
43144        if (track.type === 'audio') {
43145          trackFragmentRun = trun(track, dataOffset);
43146          return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
43147        } // video tracks should contain an independent and disposable samples
43148        // box (sdtp)
43149        // generate one and adjust offsets to match
43150
43151
43152        sampleDependencyTable = sdtp(track);
43153        trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
43154        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
43155      };
43156      /**
43157       * Generate a track box.
43158       * @param track {object} a track definition
43159       * @return {Uint8Array} the track box
43160       */
43161
43162
43163      trak = function trak(track) {
43164        track.duration = track.duration || 0xffffffff;
43165        return box(types.trak, tkhd(track), mdia(track));
43166      };
43167
43168      trex = function trex(track) {
43169        var result = new Uint8Array([0x00, // version 0
43170        0x00, 0x00, 0x00, // flags
43171        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43172        0x00, 0x00, 0x00, 0x01, // default_sample_description_index
43173        0x00, 0x00, 0x00, 0x00, // default_sample_duration
43174        0x00, 0x00, 0x00, 0x00, // default_sample_size
43175        0x00, 0x01, 0x00, 0x01 // default_sample_flags
43176        ]); // the last two bytes of default_sample_flags is the sample
43177        // degradation priority, a hint about the importance of this sample
43178        // relative to others. Lower the degradation priority for all sample
43179        // types other than video.
43180
43181        if (track.type !== 'video') {
43182          result[result.length - 1] = 0x00;
43183        }
43184
43185        return box(types.trex, result);
43186      };
43187
43188      (function () {
43189        var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
43190        // duration is present for the first sample, it will be present for
43191        // all subsequent samples.
43192        // see ISO/IEC 14496-12:2012, Section 8.8.8.1
43193
43194        trunHeader = function trunHeader(samples, offset) {
43195          var durationPresent = 0,
43196              sizePresent = 0,
43197              flagsPresent = 0,
43198              compositionTimeOffset = 0; // trun flag constants
43199
43200          if (samples.length) {
43201            if (samples[0].duration !== undefined) {
43202              durationPresent = 0x1;
43203            }
43204
43205            if (samples[0].size !== undefined) {
43206              sizePresent = 0x2;
43207            }
43208
43209            if (samples[0].flags !== undefined) {
43210              flagsPresent = 0x4;
43211            }
43212
43213            if (samples[0].compositionTimeOffset !== undefined) {
43214              compositionTimeOffset = 0x8;
43215            }
43216          }
43217
43218          return [0x00, // version 0
43219          0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
43220          (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
43221          (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
43222          ];
43223        };
43224
43225        videoTrun = function videoTrun(track, offset) {
43226          var bytesOffest, bytes, header, samples, sample, i;
43227          samples = track.samples || [];
43228          offset += 8 + 12 + 16 * samples.length;
43229          header = trunHeader(samples, offset);
43230          bytes = new Uint8Array(header.length + samples.length * 16);
43231          bytes.set(header);
43232          bytesOffest = header.length;
43233
43234          for (i = 0; i < samples.length; i++) {
43235            sample = samples[i];
43236            bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43237            bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43238            bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43239            bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43240
43241            bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43242            bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43243            bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43244            bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43245
43246            bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
43247            bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
43248            bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
43249            bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
43250
43251            bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
43252            bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
43253            bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
43254            bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
43255          }
43256
43257          return box(types.trun, bytes);
43258        };
43259
43260        audioTrun = function audioTrun(track, offset) {
43261          var bytes, bytesOffest, header, samples, sample, i;
43262          samples = track.samples || [];
43263          offset += 8 + 12 + 8 * samples.length;
43264          header = trunHeader(samples, offset);
43265          bytes = new Uint8Array(header.length + samples.length * 8);
43266          bytes.set(header);
43267          bytesOffest = header.length;
43268
43269          for (i = 0; i < samples.length; i++) {
43270            sample = samples[i];
43271            bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43272            bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43273            bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43274            bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43275
43276            bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43277            bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43278            bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43279            bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43280          }
43281
43282          return box(types.trun, bytes);
43283        };
43284
43285        trun = function trun(track, offset) {
43286          if (track.type === 'audio') {
43287            return audioTrun(track, offset);
43288          }
43289
43290          return videoTrun(track, offset);
43291        };
43292      })();
43293
43294      var mp4Generator = {
43295        ftyp: ftyp,
43296        mdat: mdat,
43297        moof: moof,
43298        moov: moov,
43299        initSegment: function initSegment(tracks) {
43300          var fileType = ftyp(),
43301              movie = moov(tracks),
43302              result;
43303          result = new Uint8Array(fileType.byteLength + movie.byteLength);
43304          result.set(fileType);
43305          result.set(movie, fileType.byteLength);
43306          return result;
43307        }
43308      };
43309      /**
43310       * mux.js
43311       *
43312       * Copyright (c) Brightcove
43313       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43314       */
43315      // Convert an array of nal units into an array of frames with each frame being
43316      // composed of the nal units that make up that frame
43317      // Also keep track of cummulative data about the frame from the nal units such
43318      // as the frame duration, starting pts, etc.
43319
43320      var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
43321        var i,
43322            currentNal,
43323            currentFrame = [],
43324            frames = []; // TODO added for LHLS, make sure this is OK
43325
43326        frames.byteLength = 0;
43327        frames.nalCount = 0;
43328        frames.duration = 0;
43329        currentFrame.byteLength = 0;
43330
43331        for (i = 0; i < nalUnits.length; i++) {
43332          currentNal = nalUnits[i]; // Split on 'aud'-type nal units
43333
43334          if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
43335            // Since the very first nal unit is expected to be an AUD
43336            // only push to the frames array when currentFrame is not empty
43337            if (currentFrame.length) {
43338              currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
43339
43340              frames.byteLength += currentFrame.byteLength;
43341              frames.nalCount += currentFrame.length;
43342              frames.duration += currentFrame.duration;
43343              frames.push(currentFrame);
43344            }
43345
43346            currentFrame = [currentNal];
43347            currentFrame.byteLength = currentNal.data.byteLength;
43348            currentFrame.pts = currentNal.pts;
43349            currentFrame.dts = currentNal.dts;
43350          } else {
43351            // Specifically flag key frames for ease of use later
43352            if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
43353              currentFrame.keyFrame = true;
43354            }
43355
43356            currentFrame.duration = currentNal.dts - currentFrame.dts;
43357            currentFrame.byteLength += currentNal.data.byteLength;
43358            currentFrame.push(currentNal);
43359          }
43360        } // For the last frame, use the duration of the previous frame if we
43361        // have nothing better to go on
43362
43363
43364        if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
43365          currentFrame.duration = frames[frames.length - 1].duration;
43366        } // Push the final frame
43367        // TODO added for LHLS, make sure this is OK
43368
43369
43370        frames.byteLength += currentFrame.byteLength;
43371        frames.nalCount += currentFrame.length;
43372        frames.duration += currentFrame.duration;
43373        frames.push(currentFrame);
43374        return frames;
43375      }; // Convert an array of frames into an array of Gop with each Gop being composed
43376      // of the frames that make up that Gop
43377      // Also keep track of cummulative data about the Gop from the frames such as the
43378      // Gop duration, starting pts, etc.
43379
43380
43381      var groupFramesIntoGops = function groupFramesIntoGops(frames) {
43382        var i,
43383            currentFrame,
43384            currentGop = [],
43385            gops = []; // We must pre-set some of the values on the Gop since we
43386        // keep running totals of these values
43387
43388        currentGop.byteLength = 0;
43389        currentGop.nalCount = 0;
43390        currentGop.duration = 0;
43391        currentGop.pts = frames[0].pts;
43392        currentGop.dts = frames[0].dts; // store some metadata about all the Gops
43393
43394        gops.byteLength = 0;
43395        gops.nalCount = 0;
43396        gops.duration = 0;
43397        gops.pts = frames[0].pts;
43398        gops.dts = frames[0].dts;
43399
43400        for (i = 0; i < frames.length; i++) {
43401          currentFrame = frames[i];
43402
43403          if (currentFrame.keyFrame) {
43404            // Since the very first frame is expected to be an keyframe
43405            // only push to the gops array when currentGop is not empty
43406            if (currentGop.length) {
43407              gops.push(currentGop);
43408              gops.byteLength += currentGop.byteLength;
43409              gops.nalCount += currentGop.nalCount;
43410              gops.duration += currentGop.duration;
43411            }
43412
43413            currentGop = [currentFrame];
43414            currentGop.nalCount = currentFrame.length;
43415            currentGop.byteLength = currentFrame.byteLength;
43416            currentGop.pts = currentFrame.pts;
43417            currentGop.dts = currentFrame.dts;
43418            currentGop.duration = currentFrame.duration;
43419          } else {
43420            currentGop.duration += currentFrame.duration;
43421            currentGop.nalCount += currentFrame.length;
43422            currentGop.byteLength += currentFrame.byteLength;
43423            currentGop.push(currentFrame);
43424          }
43425        }
43426
43427        if (gops.length && currentGop.duration <= 0) {
43428          currentGop.duration = gops[gops.length - 1].duration;
43429        }
43430
43431        gops.byteLength += currentGop.byteLength;
43432        gops.nalCount += currentGop.nalCount;
43433        gops.duration += currentGop.duration; // push the final Gop
43434
43435        gops.push(currentGop);
43436        return gops;
43437      };
43438      /*
43439       * Search for the first keyframe in the GOPs and throw away all frames
43440       * until that keyframe. Then extend the duration of the pulled keyframe
43441       * and pull the PTS and DTS of the keyframe so that it covers the time
43442       * range of the frames that were disposed.
43443       *
43444       * @param {Array} gops video GOPs
43445       * @returns {Array} modified video GOPs
43446       */
43447
43448
43449      var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
43450        var currentGop;
43451
43452        if (!gops[0][0].keyFrame && gops.length > 1) {
43453          // Remove the first GOP
43454          currentGop = gops.shift();
43455          gops.byteLength -= currentGop.byteLength;
43456          gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
43457          // first gop to cover the time period of the
43458          // frames we just removed
43459
43460          gops[0][0].dts = currentGop.dts;
43461          gops[0][0].pts = currentGop.pts;
43462          gops[0][0].duration += currentGop.duration;
43463        }
43464
43465        return gops;
43466      };
43467      /**
43468       * Default sample object
43469       * see ISO/IEC 14496-12:2012, section 8.6.4.3
43470       */
43471
43472
43473      var createDefaultSample = function createDefaultSample() {
43474        return {
43475          size: 0,
43476          flags: {
43477            isLeading: 0,
43478            dependsOn: 1,
43479            isDependedOn: 0,
43480            hasRedundancy: 0,
43481            degradationPriority: 0,
43482            isNonSyncSample: 1
43483          }
43484        };
43485      };
43486      /*
43487       * Collates information from a video frame into an object for eventual
43488       * entry into an MP4 sample table.
43489       *
43490       * @param {Object} frame the video frame
43491       * @param {Number} dataOffset the byte offset to position the sample
43492       * @return {Object} object containing sample table info for a frame
43493       */
43494
43495
43496      var sampleForFrame = function sampleForFrame(frame, dataOffset) {
43497        var sample = createDefaultSample();
43498        sample.dataOffset = dataOffset;
43499        sample.compositionTimeOffset = frame.pts - frame.dts;
43500        sample.duration = frame.duration;
43501        sample.size = 4 * frame.length; // Space for nal unit size
43502
43503        sample.size += frame.byteLength;
43504
43505        if (frame.keyFrame) {
43506          sample.flags.dependsOn = 2;
43507          sample.flags.isNonSyncSample = 0;
43508        }
43509
43510        return sample;
43511      }; // generate the track's sample table from an array of gops
43512
43513
43514      var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
43515        var h,
43516            i,
43517            sample,
43518            currentGop,
43519            currentFrame,
43520            dataOffset = baseDataOffset || 0,
43521            samples = [];
43522
43523        for (h = 0; h < gops.length; h++) {
43524          currentGop = gops[h];
43525
43526          for (i = 0; i < currentGop.length; i++) {
43527            currentFrame = currentGop[i];
43528            sample = sampleForFrame(currentFrame, dataOffset);
43529            dataOffset += sample.size;
43530            samples.push(sample);
43531          }
43532        }
43533
43534        return samples;
43535      }; // generate the track's raw mdat data from an array of gops
43536
43537
43538      var concatenateNalData = function concatenateNalData(gops) {
43539        var h,
43540            i,
43541            j,
43542            currentGop,
43543            currentFrame,
43544            currentNal,
43545            dataOffset = 0,
43546            nalsByteLength = gops.byteLength,
43547            numberOfNals = gops.nalCount,
43548            totalByteLength = nalsByteLength + 4 * numberOfNals,
43549            data = new Uint8Array(totalByteLength),
43550            view = new DataView(data.buffer); // For each Gop..
43551
43552        for (h = 0; h < gops.length; h++) {
43553          currentGop = gops[h]; // For each Frame..
43554
43555          for (i = 0; i < currentGop.length; i++) {
43556            currentFrame = currentGop[i]; // For each NAL..
43557
43558            for (j = 0; j < currentFrame.length; j++) {
43559              currentNal = currentFrame[j];
43560              view.setUint32(dataOffset, currentNal.data.byteLength);
43561              dataOffset += 4;
43562              data.set(currentNal.data, dataOffset);
43563              dataOffset += currentNal.data.byteLength;
43564            }
43565          }
43566        }
43567
43568        return data;
43569      }; // generate the track's sample table from a frame
43570
43571
43572      var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
43573        var sample,
43574            dataOffset = baseDataOffset || 0,
43575            samples = [];
43576        sample = sampleForFrame(frame, dataOffset);
43577        samples.push(sample);
43578        return samples;
43579      }; // generate the track's raw mdat data from a frame
43580
43581
43582      var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
43583        var i,
43584            currentNal,
43585            dataOffset = 0,
43586            nalsByteLength = frame.byteLength,
43587            numberOfNals = frame.length,
43588            totalByteLength = nalsByteLength + 4 * numberOfNals,
43589            data = new Uint8Array(totalByteLength),
43590            view = new DataView(data.buffer); // For each NAL..
43591
43592        for (i = 0; i < frame.length; i++) {
43593          currentNal = frame[i];
43594          view.setUint32(dataOffset, currentNal.data.byteLength);
43595          dataOffset += 4;
43596          data.set(currentNal.data, dataOffset);
43597          dataOffset += currentNal.data.byteLength;
43598        }
43599
43600        return data;
43601      };
43602
43603      var frameUtils = {
43604        groupNalsIntoFrames: groupNalsIntoFrames,
43605        groupFramesIntoGops: groupFramesIntoGops,
43606        extendFirstKeyFrame: extendFirstKeyFrame,
43607        generateSampleTable: generateSampleTable,
43608        concatenateNalData: concatenateNalData,
43609        generateSampleTableForFrame: generateSampleTableForFrame,
43610        concatenateNalDataForFrame: concatenateNalDataForFrame
43611      };
43612      /**
43613       * mux.js
43614       *
43615       * Copyright (c) Brightcove
43616       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43617       */
43618
43619      var highPrefix = [33, 16, 5, 32, 164, 27];
43620      var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
43621
43622      var zeroFill = function zeroFill(count) {
43623        var a = [];
43624
43625        while (count--) {
43626          a.push(0);
43627        }
43628
43629        return a;
43630      };
43631
43632      var makeTable = function makeTable(metaTable) {
43633        return Object.keys(metaTable).reduce(function (obj, key) {
43634          obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
43635            return arr.concat(part);
43636          }, []));
43637          return obj;
43638        }, {});
43639      };
43640
43641      var silence;
43642
43643      var silence_1 = function silence_1() {
43644        if (!silence) {
43645          // Frames-of-silence to use for filling in missing AAC frames
43646          var coneOfSilence = {
43647            96000: [highPrefix, [227, 64], zeroFill(154), [56]],
43648            88200: [highPrefix, [231], zeroFill(170), [56]],
43649            64000: [highPrefix, [248, 192], zeroFill(240), [56]],
43650            48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
43651            44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
43652            32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
43653            24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
43654            16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
43655            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]],
43656            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]],
43657            8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
43658          };
43659          silence = makeTable(coneOfSilence);
43660        }
43661
43662        return silence;
43663      };
43664      /**
43665       * mux.js
43666       *
43667       * Copyright (c) Brightcove
43668       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43669       */
43670
43671
43672      var ONE_SECOND_IN_TS = 90000,
43673          // 90kHz clock
43674      secondsToVideoTs,
43675          secondsToAudioTs,
43676          videoTsToSeconds,
43677          audioTsToSeconds,
43678          audioTsToVideoTs,
43679          videoTsToAudioTs,
43680          metadataTsToSeconds;
43681
43682      secondsToVideoTs = function secondsToVideoTs(seconds) {
43683        return seconds * ONE_SECOND_IN_TS;
43684      };
43685
43686      secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
43687        return seconds * sampleRate;
43688      };
43689
43690      videoTsToSeconds = function videoTsToSeconds(timestamp) {
43691        return timestamp / ONE_SECOND_IN_TS;
43692      };
43693
43694      audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
43695        return timestamp / sampleRate;
43696      };
43697
43698      audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
43699        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
43700      };
43701
43702      videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
43703        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
43704      };
43705      /**
43706       * Adjust ID3 tag or caption timing information by the timeline pts values
43707       * (if keepOriginalTimestamps is false) and convert to seconds
43708       */
43709
43710
43711      metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
43712        return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
43713      };
43714
43715      var clock = {
43716        ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
43717        secondsToVideoTs: secondsToVideoTs,
43718        secondsToAudioTs: secondsToAudioTs,
43719        videoTsToSeconds: videoTsToSeconds,
43720        audioTsToSeconds: audioTsToSeconds,
43721        audioTsToVideoTs: audioTsToVideoTs,
43722        videoTsToAudioTs: videoTsToAudioTs,
43723        metadataTsToSeconds: metadataTsToSeconds
43724      };
43725      var clock_2 = clock.secondsToVideoTs;
43726      var clock_4 = clock.videoTsToSeconds;
43727      /**
43728       * mux.js
43729       *
43730       * Copyright (c) Brightcove
43731       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43732       */
43733
43734      /**
43735       * Sum the `byteLength` properties of the data in each AAC frame
43736       */
43737
43738      var sumFrameByteLengths = function sumFrameByteLengths(array) {
43739        var i,
43740            currentObj,
43741            sum = 0; // sum the byteLength's all each nal unit in the frame
43742
43743        for (i = 0; i < array.length; i++) {
43744          currentObj = array[i];
43745          sum += currentObj.data.byteLength;
43746        }
43747
43748        return sum;
43749      }; // Possibly pad (prefix) the audio track with silence if appending this track
43750      // would lead to the introduction of a gap in the audio buffer
43751
43752
43753      var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
43754        var baseMediaDecodeTimeTs,
43755            frameDuration = 0,
43756            audioGapDuration = 0,
43757            audioFillFrameCount = 0,
43758            audioFillDuration = 0,
43759            silentFrame,
43760            i,
43761            firstFrame;
43762
43763        if (!frames.length) {
43764          return;
43765        }
43766
43767        baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
43768
43769        frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
43770
43771        if (audioAppendStartTs && videoBaseMediaDecodeTime) {
43772          // insert the shortest possible amount (audio gap or audio to video gap)
43773          audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
43774
43775          audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
43776          audioFillDuration = audioFillFrameCount * frameDuration;
43777        } // don't attempt to fill gaps smaller than a single frame or larger
43778        // than a half second
43779
43780
43781        if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
43782          return;
43783        }
43784
43785        silentFrame = silence_1()[track.samplerate];
43786
43787        if (!silentFrame) {
43788          // we don't have a silent frame pregenerated for the sample rate, so use a frame
43789          // from the content instead
43790          silentFrame = frames[0].data;
43791        }
43792
43793        for (i = 0; i < audioFillFrameCount; i++) {
43794          firstFrame = frames[0];
43795          frames.splice(0, 0, {
43796            data: silentFrame,
43797            dts: firstFrame.dts - frameDuration,
43798            pts: firstFrame.pts - frameDuration
43799          });
43800        }
43801
43802        track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
43803      }; // If the audio segment extends before the earliest allowed dts
43804      // value, remove AAC frames until starts at or after the earliest
43805      // allowed DTS so that we don't end up with a negative baseMedia-
43806      // DecodeTime for the audio track
43807
43808
43809      var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
43810        if (track.minSegmentDts >= earliestAllowedDts) {
43811          return adtsFrames;
43812        } // We will need to recalculate the earliest segment Dts
43813
43814
43815        track.minSegmentDts = Infinity;
43816        return adtsFrames.filter(function (currentFrame) {
43817          // If this is an allowed frame, keep it and record it's Dts
43818          if (currentFrame.dts >= earliestAllowedDts) {
43819            track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
43820            track.minSegmentPts = track.minSegmentDts;
43821            return true;
43822          } // Otherwise, discard it
43823
43824
43825          return false;
43826        });
43827      }; // generate the track's raw mdat data from an array of frames
43828
43829
43830      var generateSampleTable$1 = function generateSampleTable$1(frames) {
43831        var i,
43832            currentFrame,
43833            samples = [];
43834
43835        for (i = 0; i < frames.length; i++) {
43836          currentFrame = frames[i];
43837          samples.push({
43838            size: currentFrame.data.byteLength,
43839            duration: 1024 // For AAC audio, all samples contain 1024 samples
43840
43841          });
43842        }
43843
43844        return samples;
43845      }; // generate the track's sample table from an array of frames
43846
43847
43848      var concatenateFrameData = function concatenateFrameData(frames) {
43849        var i,
43850            currentFrame,
43851            dataOffset = 0,
43852            data = new Uint8Array(sumFrameByteLengths(frames));
43853
43854        for (i = 0; i < frames.length; i++) {
43855          currentFrame = frames[i];
43856          data.set(currentFrame.data, dataOffset);
43857          dataOffset += currentFrame.data.byteLength;
43858        }
43859
43860        return data;
43861      };
43862
43863      var audioFrameUtils = {
43864        prefixWithSilence: prefixWithSilence,
43865        trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
43866        generateSampleTable: generateSampleTable$1,
43867        concatenateFrameData: concatenateFrameData
43868      };
43869      /**
43870       * mux.js
43871       *
43872       * Copyright (c) Brightcove
43873       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43874       */
43875
43876      var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
43877      /**
43878       * Store information about the start and end of the track and the
43879       * duration for each frame/sample we process in order to calculate
43880       * the baseMediaDecodeTime
43881       */
43882
43883      var collectDtsInfo = function collectDtsInfo(track, data) {
43884        if (typeof data.pts === 'number') {
43885          if (track.timelineStartInfo.pts === undefined) {
43886            track.timelineStartInfo.pts = data.pts;
43887          }
43888
43889          if (track.minSegmentPts === undefined) {
43890            track.minSegmentPts = data.pts;
43891          } else {
43892            track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
43893          }
43894
43895          if (track.maxSegmentPts === undefined) {
43896            track.maxSegmentPts = data.pts;
43897          } else {
43898            track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
43899          }
43900        }
43901
43902        if (typeof data.dts === 'number') {
43903          if (track.timelineStartInfo.dts === undefined) {
43904            track.timelineStartInfo.dts = data.dts;
43905          }
43906
43907          if (track.minSegmentDts === undefined) {
43908            track.minSegmentDts = data.dts;
43909          } else {
43910            track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
43911          }
43912
43913          if (track.maxSegmentDts === undefined) {
43914            track.maxSegmentDts = data.dts;
43915          } else {
43916            track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
43917          }
43918        }
43919      };
43920      /**
43921       * Clear values used to calculate the baseMediaDecodeTime between
43922       * tracks
43923       */
43924
43925
43926      var clearDtsInfo = function clearDtsInfo(track) {
43927        delete track.minSegmentDts;
43928        delete track.maxSegmentDts;
43929        delete track.minSegmentPts;
43930        delete track.maxSegmentPts;
43931      };
43932      /**
43933       * Calculate the track's baseMediaDecodeTime based on the earliest
43934       * DTS the transmuxer has ever seen and the minimum DTS for the
43935       * current track
43936       * @param track {object} track metadata configuration
43937       * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
43938       *        in the source; false to adjust the first segment to start at 0.
43939       */
43940
43941
43942      var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
43943        var baseMediaDecodeTime,
43944            scale,
43945            minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
43946
43947        if (!keepOriginalTimestamps) {
43948          minSegmentDts -= track.timelineStartInfo.dts;
43949        } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
43950        // we want the start of the first segment to be placed
43951
43952
43953        baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
43954
43955        baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
43956
43957        baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
43958
43959        if (track.type === 'audio') {
43960          // Audio has a different clock equal to the sampling_rate so we need to
43961          // scale the PTS values into the clock rate of the track
43962          scale = track.samplerate / ONE_SECOND_IN_TS$1;
43963          baseMediaDecodeTime *= scale;
43964          baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
43965        }
43966
43967        return baseMediaDecodeTime;
43968      };
43969
43970      var trackDecodeInfo = {
43971        clearDtsInfo: clearDtsInfo,
43972        calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
43973        collectDtsInfo: collectDtsInfo
43974      };
43975      /**
43976       * mux.js
43977       *
43978       * Copyright (c) Brightcove
43979       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43980       *
43981       * Reads in-band caption information from a video elementary
43982       * stream. Captions must follow the CEA-708 standard for injection
43983       * into an MPEG-2 transport streams.
43984       * @see https://en.wikipedia.org/wiki/CEA-708
43985       * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
43986       */
43987      // Supplemental enhancement information (SEI) NAL units have a
43988      // payload type field to indicate how they are to be
43989      // interpreted. CEAS-708 caption content is always transmitted with
43990      // payload type 0x04.
43991
43992      var USER_DATA_REGISTERED_ITU_T_T35 = 4,
43993          RBSP_TRAILING_BITS = 128;
43994      /**
43995        * Parse a supplemental enhancement information (SEI) NAL unit.
43996        * Stops parsing once a message of type ITU T T35 has been found.
43997        *
43998        * @param bytes {Uint8Array} the bytes of a SEI NAL unit
43999        * @return {object} the parsed SEI payload
44000        * @see Rec. ITU-T H.264, 7.3.2.3.1
44001        */
44002
44003      var parseSei = function parseSei(bytes) {
44004        var i = 0,
44005            result = {
44006          payloadType: -1,
44007          payloadSize: 0
44008        },
44009            payloadType = 0,
44010            payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
44011
44012        while (i < bytes.byteLength) {
44013          // stop once we have hit the end of the sei_rbsp
44014          if (bytes[i] === RBSP_TRAILING_BITS) {
44015            break;
44016          } // Parse payload type
44017
44018
44019          while (bytes[i] === 0xFF) {
44020            payloadType += 255;
44021            i++;
44022          }
44023
44024          payloadType += bytes[i++]; // Parse payload size
44025
44026          while (bytes[i] === 0xFF) {
44027            payloadSize += 255;
44028            i++;
44029          }
44030
44031          payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
44032          // there can only ever be one caption message in a frame's sei
44033
44034          if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
44035            var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
44036
44037            if (userIdentifier === 'GA94') {
44038              result.payloadType = payloadType;
44039              result.payloadSize = payloadSize;
44040              result.payload = bytes.subarray(i, i + payloadSize);
44041              break;
44042            } else {
44043              result.payload = void 0;
44044            }
44045          } // skip the payload and parse the next message
44046
44047
44048          i += payloadSize;
44049          payloadType = 0;
44050          payloadSize = 0;
44051        }
44052
44053        return result;
44054      }; // see ANSI/SCTE 128-1 (2013), section 8.1
44055
44056
44057      var parseUserData = function parseUserData(sei) {
44058        // itu_t_t35_contry_code must be 181 (United States) for
44059        // captions
44060        if (sei.payload[0] !== 181) {
44061          return null;
44062        } // itu_t_t35_provider_code should be 49 (ATSC) for captions
44063
44064
44065        if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
44066          return null;
44067        } // the user_identifier should be "GA94" to indicate ATSC1 data
44068
44069
44070        if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
44071          return null;
44072        } // finally, user_data_type_code should be 0x03 for caption data
44073
44074
44075        if (sei.payload[7] !== 0x03) {
44076          return null;
44077        } // return the user_data_type_structure and strip the trailing
44078        // marker bits
44079
44080
44081        return sei.payload.subarray(8, sei.payload.length - 1);
44082      }; // see CEA-708-D, section 4.4
44083
44084
44085      var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
44086        var results = [],
44087            i,
44088            count,
44089            offset,
44090            data; // if this is just filler, return immediately
44091
44092        if (!(userData[0] & 0x40)) {
44093          return results;
44094        } // parse out the cc_data_1 and cc_data_2 fields
44095
44096
44097        count = userData[0] & 0x1f;
44098
44099        for (i = 0; i < count; i++) {
44100          offset = i * 3;
44101          data = {
44102            type: userData[offset + 2] & 0x03,
44103            pts: pts
44104          }; // capture cc data when cc_valid is 1
44105
44106          if (userData[offset + 2] & 0x04) {
44107            data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
44108            results.push(data);
44109          }
44110        }
44111
44112        return results;
44113      };
44114
44115      var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
44116        var length = data.byteLength,
44117            emulationPreventionBytesPositions = [],
44118            i = 1,
44119            newLength,
vendor: 4,053 bytes, lines 44120-44224
44120            newData; // Find all `Emulation Prevention Bytes`
44121
44122        while (i < length - 2) {
44123          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
44124            emulationPreventionBytesPositions.push(i + 2);
44125            i += 2;
44126          } else {
44127            i++;
44128          }
44129        } // If no Emulation Prevention Bytes were found just return the original
44130        // array
44131
44132
44133        if (emulationPreventionBytesPositions.length === 0) {
44134          return data;
44135        } // Create a new array to hold the NAL unit data
44136
44137
44138        newLength = length - emulationPreventionBytesPositions.length;
44139        newData = new Uint8Array(newLength);
44140        var sourceIndex = 0;
44141
44142        for (i = 0; i < newLength; sourceIndex++, i++) {
44143          if (sourceIndex === emulationPreventionBytesPositions[0]) {
44144            // Skip this byte
44145            sourceIndex++; // Remove this position index
44146
44147            emulationPreventionBytesPositions.shift();
44148          }
44149
44150          newData[i] = data[sourceIndex];
44151        }
44152
44153        return newData;
44154      }; // exports
44155
44156
44157      var captionPacketParser = {
44158        parseSei: parseSei,
44159        parseUserData: parseUserData,
44160        parseCaptionPackets: parseCaptionPackets,
44161        discardEmulationPreventionBytes: discardEmulationPreventionBytes,
44162        USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
44163      }; // -----------------
44164      // Link To Transport
44165      // -----------------
44166
44167      var CaptionStream = function CaptionStream() {
44168        CaptionStream.prototype.init.call(this);
44169        this.captionPackets_ = [];
44170        this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
44171        new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
44172        new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
44173        new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
44174        ];
44175        this.reset(); // forward data and done events from CCs to this CaptionStream
44176
44177        this.ccStreams_.forEach(function (cc) {
44178          cc.on('data', this.trigger.bind(this, 'data'));
44179          cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
44180          cc.on('done', this.trigger.bind(this, 'done'));
44181        }, this);
44182      };
44183
44184      CaptionStream.prototype = new stream();
44185
44186      CaptionStream.prototype.push = function (event) {
44187        var sei, userData, newCaptionPackets; // only examine SEI NALs
44188
44189        if (event.nalUnitType !== 'sei_rbsp') {
44190          return;
44191        } // parse the sei
44192
44193
44194        sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
44195
44196        if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
44197          return;
44198        } // parse out the user data payload
44199
44200
44201        userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
44202
44203        if (!userData) {
44204          return;
44205        } // Sometimes, the same segment # will be downloaded twice. To stop the
44206        // caption data from being processed twice, we track the latest dts we've
44207        // received and ignore everything with a dts before that. However, since
44208        // data for a specific dts can be split across packets on either side of
44209        // a segment boundary, we need to make sure we *don't* ignore the packets
44210        // from the *next* segment that have dts === this.latestDts_. By constantly
44211        // tracking the number of packets received with dts === this.latestDts_, we
44212        // know how many should be ignored once we start receiving duplicates.
44213
44214
44215        if (event.dts < this.latestDts_) {
44216          // We've started getting older data, so set the flag.
44217          this.ignoreNextEqualDts_ = true;
44218          return;
44219        } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
44220          this.numSameDts_--;
44221
44222          if (!this.numSameDts_) {
44223            // We've received the last duplicate packet, time to start processing again
44224            this.ignoreNextEqualDts_ = false;
vendor: 7,286 bytes, lines 44225-44471
44225          }
44226
44227          return;
44228        } // parse out CC data packets and save them for later
44229
44230
44231        newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
44232        this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
44233
44234        if (this.latestDts_ !== event.dts) {
44235          this.numSameDts_ = 0;
44236        }
44237
44238        this.numSameDts_++;
44239        this.latestDts_ = event.dts;
44240      };
44241
44242      CaptionStream.prototype.flushCCStreams = function (flushType) {
44243        this.ccStreams_.forEach(function (cc) {
44244          return flushType === 'flush' ? cc.flush() : cc.partialFlush();
44245        }, this);
44246      };
44247
44248      CaptionStream.prototype.flushStream = function (flushType) {
44249        // make sure we actually parsed captions before proceeding
44250        if (!this.captionPackets_.length) {
44251          this.flushCCStreams(flushType);
44252          return;
44253        } // In Chrome, the Array#sort function is not stable so add a
44254        // presortIndex that we can use to ensure we get a stable-sort
44255
44256
44257        this.captionPackets_.forEach(function (elem, idx) {
44258          elem.presortIndex = idx;
44259        }); // sort caption byte-pairs based on their PTS values
44260
44261        this.captionPackets_.sort(function (a, b) {
44262          if (a.pts === b.pts) {
44263            return a.presortIndex - b.presortIndex;
44264          }
44265
44266          return a.pts - b.pts;
44267        });
44268        this.captionPackets_.forEach(function (packet) {
44269          if (packet.type < 2) {
44270            // Dispatch packet to the right Cea608Stream
44271            this.dispatchCea608Packet(packet);
44272          } // this is where an 'else' would go for a dispatching packets
44273          // to a theoretical Cea708Stream that handles SERVICEn data
44274
44275        }, this);
44276        this.captionPackets_.length = 0;
44277        this.flushCCStreams(flushType);
44278      };
44279
44280      CaptionStream.prototype.flush = function () {
44281        return this.flushStream('flush');
44282      }; // Only called if handling partial data
44283
44284
44285      CaptionStream.prototype.partialFlush = function () {
44286        return this.flushStream('partialFlush');
44287      };
44288
44289      CaptionStream.prototype.reset = function () {
44290        this.latestDts_ = null;
44291        this.ignoreNextEqualDts_ = false;
44292        this.numSameDts_ = 0;
44293        this.activeCea608Channel_ = [null, null];
44294        this.ccStreams_.forEach(function (ccStream) {
44295          ccStream.reset();
44296        });
44297      }; // From the CEA-608 spec:
44298
44299      /*
44300       * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
44301       * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
44302       * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
44303       * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
44304       * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
44305       * to switch to captioning or Text.
44306      */
44307      // With that in mind, we ignore any data between an XDS control code and a
44308      // subsequent closed-captioning control code.
44309
44310
44311      CaptionStream.prototype.dispatchCea608Packet = function (packet) {
44312        // NOTE: packet.type is the CEA608 field
44313        if (this.setsTextOrXDSActive(packet)) {
44314          this.activeCea608Channel_[packet.type] = null;
44315        } else if (this.setsChannel1Active(packet)) {
44316          this.activeCea608Channel_[packet.type] = 0;
44317        } else if (this.setsChannel2Active(packet)) {
44318          this.activeCea608Channel_[packet.type] = 1;
44319        }
44320
44321        if (this.activeCea608Channel_[packet.type] === null) {
44322          // If we haven't received anything to set the active channel, or the
44323          // packets are Text/XDS data, discard the data; we don't want jumbled
44324          // captions
44325          return;
44326        }
44327
44328        this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
44329      };
44330
44331      CaptionStream.prototype.setsChannel1Active = function (packet) {
44332        return (packet.ccData & 0x7800) === 0x1000;
44333      };
44334
44335      CaptionStream.prototype.setsChannel2Active = function (packet) {
44336        return (packet.ccData & 0x7800) === 0x1800;
44337      };
44338
44339      CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
44340        return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
44341      }; // ----------------------
44342      // Session to Application
44343      // ----------------------
44344      // This hash maps non-ASCII, special, and extended character codes to their
44345      // proper Unicode equivalent. The first keys that are only a single byte
44346      // are the non-standard ASCII characters, which simply map the CEA608 byte
44347      // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
44348      // character codes, but have their MSB bitmasked with 0x03 so that a lookup
44349      // can be performed regardless of the field and data channel on which the
44350      // character code was received.
44351
44352
44353      var CHARACTER_TRANSLATION = {
44354        0x2a: 0xe1,
44355        // á
44356        0x5c: 0xe9,
44357        // é
44358        0x5e: 0xed,
44359        // í
44360        0x5f: 0xf3,
44361        // ó
44362        0x60: 0xfa,
44363        // ú
44364        0x7b: 0xe7,
44365        // ç
44366        0x7c: 0xf7,
44367        // ÷
44368        0x7d: 0xd1,
44369        // Ñ
44370        0x7e: 0xf1,
44371        // ñ
44372        0x7f: 0x2588,
44373        // █
44374        0x0130: 0xae,
44375        // ®
44376        0x0131: 0xb0,
44377        // °
44378        0x0132: 0xbd,
44379        // ½
44380        0x0133: 0xbf,
44381        // ¿
44382        0x0134: 0x2122,
44383        // ™
44384        0x0135: 0xa2,
44385        // ¢
44386        0x0136: 0xa3,
44387        // £
44388        0x0137: 0x266a,
44389        // ♪
44390        0x0138: 0xe0,
44391        // à
44392        0x0139: 0xa0,
44393        //
44394        0x013a: 0xe8,
44395        // è
44396        0x013b: 0xe2,
44397        // â
44398        0x013c: 0xea,
44399        // ê
44400        0x013d: 0xee,
44401        // î
44402        0x013e: 0xf4,
44403        // ô
44404        0x013f: 0xfb,
44405        // û
44406        0x0220: 0xc1,
44407        // Á
44408        0x0221: 0xc9,
44409        // É
44410        0x0222: 0xd3,
44411        // Ó
44412        0x0223: 0xda,
44413        // Ú
44414        0x0224: 0xdc,
44415        // Ü
44416        0x0225: 0xfc,
44417        // ü
44418        0x0226: 0x2018,
44419        // ‘
44420        0x0227: 0xa1,
44421        // ¡
44422        0x0228: 0x2a,
44423        // *
44424        0x0229: 0x27,
44425        // '
44426        0x022a: 0x2014,
44427        // —
44428        0x022b: 0xa9,
44429        // ©
44430        0x022c: 0x2120,
44431        // ℠
44432        0x022d: 0x2022,
44433        // •
44434        0x022e: 0x201c,
44435        // “
44436        0x022f: 0x201d,
44437        // ”
44438        0x0230: 0xc0,
44439        // À
44440        0x0231: 0xc2,
44441        // Â
44442        0x0232: 0xc7,
44443        // Ç
44444        0x0233: 0xc8,
44445        // È
44446        0x0234: 0xca,
44447        // Ê
44448        0x0235: 0xcb,
44449        // Ë
44450        0x0236: 0xeb,
44451        // ë
44452        0x0237: 0xce,
44453        // Î
44454        0x0238: 0xcf,
44455        // Ï
44456        0x0239: 0xef,
44457        // ï
44458        0x023a: 0xd4,
44459        // Ô
44460        0x023b: 0xd9,
44461        // Ù
44462        0x023c: 0xf9,
44463        // ù
44464        0x023d: 0xdb,
44465        // Û
44466        0x023e: 0xab,
44467        // «
44468        0x023f: 0xbb,
44469        // »
44470        0x0320: 0xc3,
44471        // Ã
vendor: 20,265 bytes, lines 44472-45004
44472        0x0321: 0xe3,
44473        // ã
44474        0x0322: 0xcd,
44475        // Í
44476        0x0323: 0xcc,
44477        // Ì
44478        0x0324: 0xec,
44479        // ì
44480        0x0325: 0xd2,
44481        // Ò
44482        0x0326: 0xf2,
44483        // ò
44484        0x0327: 0xd5,
44485        // Õ
44486        0x0328: 0xf5,
44487        // õ
44488        0x0329: 0x7b,
44489        // {
44490        0x032a: 0x7d,
44491        // }
44492        0x032b: 0x5c,
44493        // \
44494        0x032c: 0x5e,
44495        // ^
44496        0x032d: 0x5f,
44497        // _
44498        0x032e: 0x7c,
44499        // |
44500        0x032f: 0x7e,
44501        // ~
44502        0x0330: 0xc4,
44503        // Ä
44504        0x0331: 0xe4,
44505        // ä
44506        0x0332: 0xd6,
44507        // Ö
44508        0x0333: 0xf6,
44509        // ö
44510        0x0334: 0xdf,
44511        // ß
44512        0x0335: 0xa5,
44513        // ¥
44514        0x0336: 0xa4,
44515        // ¤
44516        0x0337: 0x2502,
44517        // │
44518        0x0338: 0xc5,
44519        // Å
44520        0x0339: 0xe5,
44521        // å
44522        0x033a: 0xd8,
44523        // Ø
44524        0x033b: 0xf8,
44525        // ø
44526        0x033c: 0x250c,
44527        // ┌
44528        0x033d: 0x2510,
44529        // ┐
44530        0x033e: 0x2514,
44531        // └
44532        0x033f: 0x2518 // ┘
44533
44534      };
44535
44536      var getCharFromCode = function getCharFromCode(code) {
44537        if (code === null) {
44538          return '';
44539        }
44540
44541        code = CHARACTER_TRANSLATION[code] || code;
44542        return String.fromCharCode(code);
44543      }; // the index of the last row in a CEA-608 display buffer
44544
44545
44546      var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
44547      // getting it through bit logic.
44548
44549      var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
44550      // cells. The "bottom" row is the last element in the outer array.
44551
44552      var createDisplayBuffer = function createDisplayBuffer() {
44553        var result = [],
44554            i = BOTTOM_ROW + 1;
44555
44556        while (i--) {
44557          result.push('');
44558        }
44559
44560        return result;
44561      };
44562
44563      var Cea608Stream = function Cea608Stream(field, dataChannel) {
44564        Cea608Stream.prototype.init.call(this);
44565        this.field_ = field || 0;
44566        this.dataChannel_ = dataChannel || 0;
44567        this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
44568        this.setConstants();
44569        this.reset();
44570
44571        this.push = function (packet) {
44572          var data, swap, char0, char1, text; // remove the parity bits
44573
44574          data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
44575
44576          if (data === this.lastControlCode_) {
44577            this.lastControlCode_ = null;
44578            return;
44579          } // Store control codes
44580
44581
44582          if ((data & 0xf000) === 0x1000) {
44583            this.lastControlCode_ = data;
44584          } else if (data !== this.PADDING_) {
44585            this.lastControlCode_ = null;
44586          }
44587
44588          char0 = data >>> 8;
44589          char1 = data & 0xff;
44590
44591          if (data === this.PADDING_) {
44592            return;
44593          } else if (data === this.RESUME_CAPTION_LOADING_) {
44594            this.mode_ = 'popOn';
44595          } else if (data === this.END_OF_CAPTION_) {
44596            // If an EOC is received while in paint-on mode, the displayed caption
44597            // text should be swapped to non-displayed memory as if it was a pop-on
44598            // caption. Because of that, we should explicitly switch back to pop-on
44599            // mode
44600            this.mode_ = 'popOn';
44601            this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
44602
44603            this.flushDisplayed(packet.pts); // flip memory
44604
44605            swap = this.displayed_;
44606            this.displayed_ = this.nonDisplayed_;
44607            this.nonDisplayed_ = swap; // start measuring the time to display the caption
44608
44609            this.startPts_ = packet.pts;
44610          } else if (data === this.ROLL_UP_2_ROWS_) {
44611            this.rollUpRows_ = 2;
44612            this.setRollUp(packet.pts);
44613          } else if (data === this.ROLL_UP_3_ROWS_) {
44614            this.rollUpRows_ = 3;
44615            this.setRollUp(packet.pts);
44616          } else if (data === this.ROLL_UP_4_ROWS_) {
44617            this.rollUpRows_ = 4;
44618            this.setRollUp(packet.pts);
44619          } else if (data === this.CARRIAGE_RETURN_) {
44620            this.clearFormatting(packet.pts);
44621            this.flushDisplayed(packet.pts);
44622            this.shiftRowsUp_();
44623            this.startPts_ = packet.pts;
44624          } else if (data === this.BACKSPACE_) {
44625            if (this.mode_ === 'popOn') {
44626              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
44627            } else {
44628              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
44629            }
44630          } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
44631            this.flushDisplayed(packet.pts);
44632            this.displayed_ = createDisplayBuffer();
44633          } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
44634            this.nonDisplayed_ = createDisplayBuffer();
44635          } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
44636            if (this.mode_ !== 'paintOn') {
44637              // NOTE: This should be removed when proper caption positioning is
44638              // implemented
44639              this.flushDisplayed(packet.pts);
44640              this.displayed_ = createDisplayBuffer();
44641            }
44642
44643            this.mode_ = 'paintOn';
44644            this.startPts_ = packet.pts; // Append special characters to caption text
44645          } else if (this.isSpecialCharacter(char0, char1)) {
44646            // Bitmask char0 so that we can apply character transformations
44647            // regardless of field and data channel.
44648            // Then byte-shift to the left and OR with char1 so we can pass the
44649            // entire character code to `getCharFromCode`.
44650            char0 = (char0 & 0x03) << 8;
44651            text = getCharFromCode(char0 | char1);
44652            this[this.mode_](packet.pts, text);
44653            this.column_++; // Append extended characters to caption text
44654          } else if (this.isExtCharacter(char0, char1)) {
44655            // Extended characters always follow their "non-extended" equivalents.
44656            // IE if a "è" is desired, you'll always receive "eè"; non-compliant
44657            // decoders are supposed to drop the "è", while compliant decoders
44658            // backspace the "e" and insert "è".
44659            // Delete the previous character
44660            if (this.mode_ === 'popOn') {
44661              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
44662            } else {
44663              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
44664            } // Bitmask char0 so that we can apply character transformations
44665            // regardless of field and data channel.
44666            // Then byte-shift to the left and OR with char1 so we can pass the
44667            // entire character code to `getCharFromCode`.
44668
44669
44670            char0 = (char0 & 0x03) << 8;
44671            text = getCharFromCode(char0 | char1);
44672            this[this.mode_](packet.pts, text);
44673            this.column_++; // Process mid-row codes
44674          } else if (this.isMidRowCode(char0, char1)) {
44675            // Attributes are not additive, so clear all formatting
44676            this.clearFormatting(packet.pts); // According to the standard, mid-row codes
44677            // should be replaced with spaces, so add one now
44678
44679            this[this.mode_](packet.pts, ' ');
44680            this.column_++;
44681
44682            if ((char1 & 0xe) === 0xe) {
44683              this.addFormatting(packet.pts, ['i']);
44684            }
44685
44686            if ((char1 & 0x1) === 0x1) {
44687              this.addFormatting(packet.pts, ['u']);
44688            } // Detect offset control codes and adjust cursor
44689
44690          } else if (this.isOffsetControlCode(char0, char1)) {
44691            // Cursor position is set by indent PAC (see below) in 4-column
44692            // increments, with an additional offset code of 1-3 to reach any
44693            // of the 32 columns specified by CEA-608. So all we need to do
44694            // here is increment the column cursor by the given offset.
44695            this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
44696          } else if (this.isPAC(char0, char1)) {
44697            // There's no logic for PAC -> row mapping, so we have to just
44698            // find the row code in an array and use its index :(
44699            var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
44700
44701            if (this.mode_ === 'rollUp') {
44702              // This implies that the base row is incorrectly set.
44703              // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
44704              // of roll-up rows set.
44705              if (row - this.rollUpRows_ + 1 < 0) {
44706                row = this.rollUpRows_ - 1;
44707              }
44708
44709              this.setRollUp(packet.pts, row);
44710            }
44711
44712            if (row !== this.row_) {
44713              // formatting is only persistent for current row
44714              this.clearFormatting(packet.pts);
44715              this.row_ = row;
44716            } // All PACs can apply underline, so detect and apply
44717            // (All odd-numbered second bytes set underline)
44718
44719
44720            if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
44721              this.addFormatting(packet.pts, ['u']);
44722            }
44723
44724            if ((data & 0x10) === 0x10) {
44725              // We've got an indent level code. Each successive even number
44726              // increments the column cursor by 4, so we can get the desired
44727              // column position by bit-shifting to the right (to get n/2)
44728              // and multiplying by 4.
44729              this.column_ = ((data & 0xe) >> 1) * 4;
44730            }
44731
44732            if (this.isColorPAC(char1)) {
44733              // it's a color code, though we only support white, which
44734              // can be either normal or italicized. white italics can be
44735              // either 0x4e or 0x6e depending on the row, so we just
44736              // bitwise-and with 0xe to see if italics should be turned on
44737              if ((char1 & 0xe) === 0xe) {
44738                this.addFormatting(packet.pts, ['i']);
44739              }
44740            } // We have a normal character in char0, and possibly one in char1
44741
44742          } else if (this.isNormalChar(char0)) {
44743            if (char1 === 0x00) {
44744              char1 = null;
44745            }
44746
44747            text = getCharFromCode(char0);
44748            text += getCharFromCode(char1);
44749            this[this.mode_](packet.pts, text);
44750            this.column_ += text.length;
44751          } // finish data processing
44752
44753        };
44754      };
44755
44756      Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
44757      // display buffer
44758
44759      Cea608Stream.prototype.flushDisplayed = function (pts) {
44760        var content = this.displayed_ // remove spaces from the start and end of the string
44761        .map(function (row) {
44762          try {
44763            return row.trim();
44764          } catch (e) {
44765            // Ordinarily, this shouldn't happen. However, caption
44766            // parsing errors should not throw exceptions and
44767            // break playback.
44768            // eslint-disable-next-line no-console
44769            console.error('Skipping malformed caption.');
44770            return '';
44771          }
44772        }) // combine all text rows to display in one cue
44773        .join('\n') // and remove blank rows from the start and end, but not the middle
44774        .replace(/^\n+|\n+$/g, '');
44775
44776        if (content.length) {
44777          this.trigger('data', {
44778            startPts: this.startPts_,
44779            endPts: pts,
44780            text: content,
44781            stream: this.name_
44782          });
44783        }
44784      };
44785      /**
44786       * Zero out the data, used for startup and on seek
44787       */
44788
44789
44790      Cea608Stream.prototype.reset = function () {
44791        this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
44792        // actually display captions. If a caption is shifted to a row
44793        // with a lower index than this, it is cleared from the display
44794        // buffer
44795
44796        this.topRow_ = 0;
44797        this.startPts_ = 0;
44798        this.displayed_ = createDisplayBuffer();
44799        this.nonDisplayed_ = createDisplayBuffer();
44800        this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
44801
44802        this.column_ = 0;
44803        this.row_ = BOTTOM_ROW;
44804        this.rollUpRows_ = 2; // This variable holds currently-applied formatting
44805
44806        this.formatting_ = [];
44807      };
44808      /**
44809       * Sets up control code and related constants for this instance
44810       */
44811
44812
44813      Cea608Stream.prototype.setConstants = function () {
44814        // The following attributes have these uses:
44815        // ext_ :    char0 for mid-row codes, and the base for extended
44816        //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
44817        //           extended codes)
44818        // control_: char0 for control codes, except byte-shifted to the
44819        //           left so that we can do this.control_ | CONTROL_CODE
44820        // offset_:  char0 for tab offset codes
44821        //
44822        // It's also worth noting that control codes, and _only_ control codes,
44823        // differ between field 1 and field2. Field 2 control codes are always
44824        // their field 1 value plus 1. That's why there's the "| field" on the
44825        // control value.
44826        if (this.dataChannel_ === 0) {
44827          this.BASE_ = 0x10;
44828          this.EXT_ = 0x11;
44829          this.CONTROL_ = (0x14 | this.field_) << 8;
44830          this.OFFSET_ = 0x17;
44831        } else if (this.dataChannel_ === 1) {
44832          this.BASE_ = 0x18;
44833          this.EXT_ = 0x19;
44834          this.CONTROL_ = (0x1c | this.field_) << 8;
44835          this.OFFSET_ = 0x1f;
44836        } // Constants for the LSByte command codes recognized by Cea608Stream. This
44837        // list is not exhaustive. For a more comprehensive listing and semantics see
44838        // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
44839        // Padding
44840
44841
44842        this.PADDING_ = 0x0000; // Pop-on Mode
44843
44844        this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
44845        this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
44846
44847        this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
44848        this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
44849        this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
44850        this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
44851
44852        this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
44853
44854        this.BACKSPACE_ = this.CONTROL_ | 0x21;
44855        this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
44856        this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
44857      };
44858      /**
44859       * Detects if the 2-byte packet data is a special character
44860       *
44861       * Special characters have a second byte in the range 0x30 to 0x3f,
44862       * with the first byte being 0x11 (for data channel 1) or 0x19 (for
44863       * data channel 2).
44864       *
44865       * @param  {Integer} char0 The first byte
44866       * @param  {Integer} char1 The second byte
44867       * @return {Boolean}       Whether the 2 bytes are an special character
44868       */
44869
44870
44871      Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
44872        return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
44873      };
44874      /**
44875       * Detects if the 2-byte packet data is an extended character
44876       *
44877       * Extended characters have a second byte in the range 0x20 to 0x3f,
44878       * with the first byte being 0x12 or 0x13 (for data channel 1) or
44879       * 0x1a or 0x1b (for data channel 2).
44880       *
44881       * @param  {Integer} char0 The first byte
44882       * @param  {Integer} char1 The second byte
44883       * @return {Boolean}       Whether the 2 bytes are an extended character
44884       */
44885
44886
44887      Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
44888        return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
44889      };
44890      /**
44891       * Detects if the 2-byte packet is a mid-row code
44892       *
44893       * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
44894       * the first byte being 0x11 (for data channel 1) or 0x19 (for data
44895       * channel 2).
44896       *
44897       * @param  {Integer} char0 The first byte
44898       * @param  {Integer} char1 The second byte
44899       * @return {Boolean}       Whether the 2 bytes are a mid-row code
44900       */
44901
44902
44903      Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
44904        return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
44905      };
44906      /**
44907       * Detects if the 2-byte packet is an offset control code
44908       *
44909       * Offset control codes have a second byte in the range 0x21 to 0x23,
44910       * with the first byte being 0x17 (for data channel 1) or 0x1f (for
44911       * data channel 2).
44912       *
44913       * @param  {Integer} char0 The first byte
44914       * @param  {Integer} char1 The second byte
44915       * @return {Boolean}       Whether the 2 bytes are an offset control code
44916       */
44917
44918
44919      Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
44920        return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
44921      };
44922      /**
44923       * Detects if the 2-byte packet is a Preamble Address Code
44924       *
44925       * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
44926       * or 0x18 to 0x1f (for data channel 2), with the second byte in the
44927       * range 0x40 to 0x7f.
44928       *
44929       * @param  {Integer} char0 The first byte
44930       * @param  {Integer} char1 The second byte
44931       * @return {Boolean}       Whether the 2 bytes are a PAC
44932       */
44933
44934
44935      Cea608Stream.prototype.isPAC = function (char0, char1) {
44936        return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
44937      };
44938      /**
44939       * Detects if a packet's second byte is in the range of a PAC color code
44940       *
44941       * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
44942       * 0x60 to 0x6f.
44943       *
44944       * @param  {Integer} char1 The second byte
44945       * @return {Boolean}       Whether the byte is a color PAC
44946       */
44947
44948
44949      Cea608Stream.prototype.isColorPAC = function (char1) {
44950        return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
44951      };
44952      /**
44953       * Detects if a single byte is in the range of a normal character
44954       *
44955       * Normal text bytes are in the range 0x20 to 0x7f.
44956       *
44957       * @param  {Integer} char  The byte
44958       * @return {Boolean}       Whether the byte is a normal character
44959       */
44960
44961
44962      Cea608Stream.prototype.isNormalChar = function (_char) {
44963        return _char >= 0x20 && _char <= 0x7f;
44964      };
44965      /**
44966       * Configures roll-up
44967       *
44968       * @param  {Integer} pts         Current PTS
44969       * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
44970       *                               a new position
44971       */
44972
44973
44974      Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
44975        // Reset the base row to the bottom row when switching modes
44976        if (this.mode_ !== 'rollUp') {
44977          this.row_ = BOTTOM_ROW;
44978          this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
44979
44980          this.flushDisplayed(pts);
44981          this.nonDisplayed_ = createDisplayBuffer();
44982          this.displayed_ = createDisplayBuffer();
44983        }
44984
44985        if (newBaseRow !== undefined && newBaseRow !== this.row_) {
44986          // move currently displayed captions (up or down) to the new base row
44987          for (var i = 0; i < this.rollUpRows_; i++) {
44988            this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
44989            this.displayed_[this.row_ - i] = '';
44990          }
44991        }
44992
44993        if (newBaseRow === undefined) {
44994          newBaseRow = this.row_;
44995        }
44996
44997        this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
44998      }; // Adds the opening HTML tag for the passed character to the caption text,
44999      // and keeps track of it for later closing
45000
45001
45002      Cea608Stream.prototype.addFormatting = function (pts, format) {
45003        this.formatting_ = this.formatting_.concat(format);
45004        var text = format.reduce(function (text, format) {
vendor: 12,842 bytes, lines 45005-45367
45005          return text + '<' + format + '>';
45006        }, '');
45007        this[this.mode_](pts, text);
45008      }; // Adds HTML closing tags for current formatting to caption text and
45009      // clears remembered formatting
45010
45011
45012      Cea608Stream.prototype.clearFormatting = function (pts) {
45013        if (!this.formatting_.length) {
45014          return;
45015        }
45016
45017        var text = this.formatting_.reverse().reduce(function (text, format) {
45018          return text + '</' + format + '>';
45019        }, '');
45020        this.formatting_ = [];
45021        this[this.mode_](pts, text);
45022      }; // Mode Implementations
45023
45024
45025      Cea608Stream.prototype.popOn = function (pts, text) {
45026        var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
45027
45028        baseRow += text;
45029        this.nonDisplayed_[this.row_] = baseRow;
45030      };
45031
45032      Cea608Stream.prototype.rollUp = function (pts, text) {
45033        var baseRow = this.displayed_[this.row_];
45034        baseRow += text;
45035        this.displayed_[this.row_] = baseRow;
45036      };
45037
45038      Cea608Stream.prototype.shiftRowsUp_ = function () {
45039        var i; // clear out inactive rows
45040
45041        for (i = 0; i < this.topRow_; i++) {
45042          this.displayed_[i] = '';
45043        }
45044
45045        for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
45046          this.displayed_[i] = '';
45047        } // shift displayed rows up
45048
45049
45050        for (i = this.topRow_; i < this.row_; i++) {
45051          this.displayed_[i] = this.displayed_[i + 1];
45052        } // clear out the bottom row
45053
45054
45055        this.displayed_[this.row_] = '';
45056      };
45057
45058      Cea608Stream.prototype.paintOn = function (pts, text) {
45059        var baseRow = this.displayed_[this.row_];
45060        baseRow += text;
45061        this.displayed_[this.row_] = baseRow;
45062      }; // exports
45063
45064
45065      var captionStream = {
45066        CaptionStream: CaptionStream,
45067        Cea608Stream: Cea608Stream
45068      };
45069      /**
45070       * mux.js
45071       *
45072       * Copyright (c) Brightcove
45073       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
45074       */
45075
45076      var streamTypes = {
45077        H264_STREAM_TYPE: 0x1B,
45078        ADTS_STREAM_TYPE: 0x0F,
45079        METADATA_STREAM_TYPE: 0x15
45080      };
45081      var MAX_TS = 8589934592;
45082      var RO_THRESH = 4294967296;
45083      var TYPE_SHARED = 'shared';
45084
45085      var handleRollover = function handleRollover(value, reference) {
45086        var direction = 1;
45087
45088        if (value > reference) {
45089          // If the current timestamp value is greater than our reference timestamp and we detect a
45090          // timestamp rollover, this means the roll over is happening in the opposite direction.
45091          // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
45092          // point will be set to a small number, e.g. 1. The user then seeks backwards over the
45093          // rollover point. In loading this segment, the timestamp values will be very large,
45094          // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
45095          // the time stamp to be `value - 2^33`.
45096          direction = -1;
45097        } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
45098        // cause an incorrect adjustment.
45099
45100
45101        while (Math.abs(reference - value) > RO_THRESH) {
45102          value += direction * MAX_TS;
45103        }
45104
45105        return value;
45106      };
45107
45108      var TimestampRolloverStream = function TimestampRolloverStream(type) {
45109        var lastDTS, referenceDTS;
45110        TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
45111        // video and audio. We could use `undefined` here, but having a string
45112        // makes debugging a little clearer.
45113
45114        this.type_ = type || TYPE_SHARED;
45115
45116        this.push = function (data) {
45117          // Any "shared" rollover streams will accept _all_ data. Otherwise,
45118          // streams will only accept data that matches their type.
45119          if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
45120            return;
45121          }
45122
45123          if (referenceDTS === undefined) {
45124            referenceDTS = data.dts;
45125          }
45126
45127          data.dts = handleRollover(data.dts, referenceDTS);
45128          data.pts = handleRollover(data.pts, referenceDTS);
45129          lastDTS = data.dts;
45130          this.trigger('data', data);
45131        };
45132
45133        this.flush = function () {
45134          referenceDTS = lastDTS;
45135          this.trigger('done');
45136        };
45137
45138        this.endTimeline = function () {
45139          this.flush();
45140          this.trigger('endedtimeline');
45141        };
45142
45143        this.discontinuity = function () {
45144          referenceDTS = void 0;
45145          lastDTS = void 0;
45146        };
45147
45148        this.reset = function () {
45149          this.discontinuity();
45150          this.trigger('reset');
45151        };
45152      };
45153
45154      TimestampRolloverStream.prototype = new stream();
45155      var timestampRolloverStream = {
45156        TimestampRolloverStream: TimestampRolloverStream,
45157        handleRollover: handleRollover
45158      };
45159
45160      var percentEncode = function percentEncode(bytes, start, end) {
45161        var i,
45162            result = '';
45163
45164        for (i = start; i < end; i++) {
45165          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
45166        }
45167
45168        return result;
45169      },
45170          // return the string representation of the specified byte range,
45171      // interpreted as UTf-8.
45172      parseUtf8 = function parseUtf8(bytes, start, end) {
45173        return decodeURIComponent(percentEncode(bytes, start, end));
45174      },
45175          // return the string representation of the specified byte range,
45176      // interpreted as ISO-8859-1.
45177      parseIso88591 = function parseIso88591(bytes, start, end) {
45178        return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
45179      },
45180          parseSyncSafeInteger = function parseSyncSafeInteger(data) {
45181        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
45182      },
45183          tagParsers = {
45184        TXXX: function TXXX(tag) {
45185          var i;
45186
45187          if (tag.data[0] !== 3) {
45188            // ignore frames with unrecognized character encodings
45189            return;
45190          }
45191
45192          for (i = 1; i < tag.data.length; i++) {
45193            if (tag.data[i] === 0) {
45194              // parse the text fields
45195              tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
45196
45197              tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
45198              break;
45199            }
45200          }
45201
45202          tag.data = tag.value;
45203        },
45204        WXXX: function WXXX(tag) {
45205          var i;
45206
45207          if (tag.data[0] !== 3) {
45208            // ignore frames with unrecognized character encodings
45209            return;
45210          }
45211
45212          for (i = 1; i < tag.data.length; i++) {
45213            if (tag.data[i] === 0) {
45214              // parse the description and URL fields
45215              tag.description = parseUtf8(tag.data, 1, i);
45216              tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
45217              break;
45218            }
45219          }
45220        },
45221        PRIV: function PRIV(tag) {
45222          var i;
45223
45224          for (i = 0; i < tag.data.length; i++) {
45225            if (tag.data[i] === 0) {
45226              // parse the description and URL fields
45227              tag.owner = parseIso88591(tag.data, 0, i);
45228              break;
45229            }
45230          }
45231
45232          tag.privateData = tag.data.subarray(i + 1);
45233          tag.data = tag.privateData;
45234        }
45235      },
45236          _MetadataStream;
45237
45238      _MetadataStream = function MetadataStream(options) {
45239        var settings = {
45240          debug: !!(options && options.debug),
45241          // the bytes of the program-level descriptor field in MP2T
45242          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
45243          // program element descriptors"
45244          descriptor: options && options.descriptor
45245        },
45246            // the total size in bytes of the ID3 tag being parsed
45247        tagSize = 0,
45248            // tag data that is not complete enough to be parsed
45249        buffer = [],
45250            // the total number of bytes currently in the buffer
45251        bufferSize = 0,
45252            i;
45253
45254        _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
45255        // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
45256
45257
45258        this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
45259
45260        if (settings.descriptor) {
45261          for (i = 0; i < settings.descriptor.length; i++) {
45262            this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
45263          }
45264        }
45265
45266        this.push = function (chunk) {
45267          var tag, frameStart, frameSize, frame, i, frameHeader;
45268
45269          if (chunk.type !== 'timed-metadata') {
45270            return;
45271          } // if data_alignment_indicator is set in the PES header,
45272          // we must have the start of a new ID3 tag. Assume anything
45273          // remaining in the buffer was malformed and throw it out
45274
45275
45276          if (chunk.dataAlignmentIndicator) {
45277            bufferSize = 0;
45278            buffer.length = 0;
45279          } // ignore events that don't look like ID3 data
45280
45281
45282          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))) {
45283            if (settings.debug) {
45284              // eslint-disable-next-line no-console
45285              console.log('Skipping unrecognized metadata packet');
45286            }
45287
45288            return;
45289          } // add this chunk to the data we've collected so far
45290
45291
45292          buffer.push(chunk);
45293          bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
45294
45295          if (buffer.length === 1) {
45296            // the frame size is transmitted as a 28-bit integer in the
45297            // last four bytes of the ID3 header.
45298            // The most significant bit of each byte is dropped and the
45299            // results concatenated to recover the actual value.
45300            tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
45301            // convenient for our comparisons to include it
45302
45303            tagSize += 10;
45304          } // if the entire frame has not arrived, wait for more data
45305
45306
45307          if (bufferSize < tagSize) {
45308            return;
45309          } // collect the entire frame so it can be parsed
45310
45311
45312          tag = {
45313            data: new Uint8Array(tagSize),
45314            frames: [],
45315            pts: buffer[0].pts,
45316            dts: buffer[0].dts
45317          };
45318
45319          for (i = 0; i < tagSize;) {
45320            tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
45321            i += buffer[0].data.byteLength;
45322            bufferSize -= buffer[0].data.byteLength;
45323            buffer.shift();
45324          } // find the start of the first frame and the end of the tag
45325
45326
45327          frameStart = 10;
45328
45329          if (tag.data[5] & 0x40) {
45330            // advance the frame start past the extended header
45331            frameStart += 4; // header size field
45332
45333            frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
45334
45335            tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
45336          } // parse one or more ID3 frames
45337          // http://id3.org/id3v2.3.0#ID3v2_frame_overview
45338
45339
45340          do {
45341            // determine the number of bytes in this frame
45342            frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
45343
45344            if (frameSize < 1) {
45345              // eslint-disable-next-line no-console
45346              return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
45347            }
45348
45349            frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
45350            frame = {
45351              id: frameHeader,
45352              data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
45353            };
45354            frame.key = frame.id;
45355
45356            if (tagParsers[frame.id]) {
45357              tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
45358              // time for raw AAC data
45359
45360              if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
45361                var d = frame.data,
45362                    size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
45363                size *= 4;
45364                size += d[7] & 0x03;
45365                frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
45366                // on the value of this frame
45367                // we couldn't have known the appropriate pts and dts before
vendor: 10,055 bytes, lines 45368-45624
45368                // parsing this ID3 tag so set those values now
45369
45370                if (tag.pts === undefined && tag.dts === undefined) {
45371                  tag.pts = frame.timeStamp;
45372                  tag.dts = frame.timeStamp;
45373                }
45374
45375                this.trigger('timestamp', frame);
45376              }
45377            }
45378
45379            tag.frames.push(frame);
45380            frameStart += 10; // advance past the frame header
45381
45382            frameStart += frameSize; // advance past the frame body
45383          } while (frameStart < tagSize);
45384
45385          this.trigger('data', tag);
45386        };
45387      };
45388
45389      _MetadataStream.prototype = new stream();
45390      var metadataStream = _MetadataStream;
45391      var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
45392
45393      var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
45394
45395
45396      var MP2T_PACKET_LENGTH = 188,
45397          // bytes
45398      SYNC_BYTE = 0x47;
45399      /**
45400       * Splits an incoming stream of binary data into MPEG-2 Transport
45401       * Stream packets.
45402       */
45403
45404      _TransportPacketStream = function TransportPacketStream() {
45405        var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
45406            bytesInBuffer = 0;
45407
45408        _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
45409
45410        /**
45411         * Split a stream of data into M2TS packets
45412        **/
45413
45414
45415        this.push = function (bytes) {
45416          var startIndex = 0,
45417              endIndex = MP2T_PACKET_LENGTH,
45418              everything; // If there are bytes remaining from the last segment, prepend them to the
45419          // bytes that were pushed in
45420
45421          if (bytesInBuffer) {
45422            everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
45423            everything.set(buffer.subarray(0, bytesInBuffer));
45424            everything.set(bytes, bytesInBuffer);
45425            bytesInBuffer = 0;
45426          } else {
45427            everything = bytes;
45428          } // While we have enough data for a packet
45429
45430
45431          while (endIndex < everything.byteLength) {
45432            // Look for a pair of start and end sync bytes in the data..
45433            if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
45434              // We found a packet so emit it and jump one whole packet forward in
45435              // the stream
45436              this.trigger('data', everything.subarray(startIndex, endIndex));
45437              startIndex += MP2T_PACKET_LENGTH;
45438              endIndex += MP2T_PACKET_LENGTH;
45439              continue;
45440            } // If we get here, we have somehow become de-synchronized and we need to step
45441            // forward one byte at a time until we find a pair of sync bytes that denote
45442            // a packet
45443
45444
45445            startIndex++;
45446            endIndex++;
45447          } // If there was some data left over at the end of the segment that couldn't
45448          // possibly be a whole packet, keep it because it might be the start of a packet
45449          // that continues in the next segment
45450
45451
45452          if (startIndex < everything.byteLength) {
45453            buffer.set(everything.subarray(startIndex), 0);
45454            bytesInBuffer = everything.byteLength - startIndex;
45455          }
45456        };
45457        /**
45458         * Passes identified M2TS packets to the TransportParseStream to be parsed
45459        **/
45460
45461
45462        this.flush = function () {
45463          // If the buffer contains a whole packet when we are being flushed, emit it
45464          // and empty the buffer. Otherwise hold onto the data because it may be
45465          // important for decoding the next segment
45466          if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
45467            this.trigger('data', buffer);
45468            bytesInBuffer = 0;
45469          }
45470
45471          this.trigger('done');
45472        };
45473
45474        this.endTimeline = function () {
45475          this.flush();
45476          this.trigger('endedtimeline');
45477        };
45478
45479        this.reset = function () {
45480          bytesInBuffer = 0;
45481          this.trigger('reset');
45482        };
45483      };
45484
45485      _TransportPacketStream.prototype = new stream();
45486      /**
45487       * Accepts an MP2T TransportPacketStream and emits data events with parsed
45488       * forms of the individual transport stream packets.
45489       */
45490
45491      _TransportParseStream = function TransportParseStream() {
45492        var parsePsi, parsePat, parsePmt, self;
45493
45494        _TransportParseStream.prototype.init.call(this);
45495
45496        self = this;
45497        this.packetsWaitingForPmt = [];
45498        this.programMapTable = undefined;
45499
45500        parsePsi = function parsePsi(payload, psi) {
45501          var offset = 0; // PSI packets may be split into multiple sections and those
45502          // sections may be split into multiple packets. If a PSI
45503          // section starts in this packet, the payload_unit_start_indicator
45504          // will be true and the first byte of the payload will indicate
45505          // the offset from the current position to the start of the
45506          // section.
45507
45508          if (psi.payloadUnitStartIndicator) {
45509            offset += payload[offset] + 1;
45510          }
45511
45512          if (psi.type === 'pat') {
45513            parsePat(payload.subarray(offset), psi);
45514          } else {
45515            parsePmt(payload.subarray(offset), psi);
45516          }
45517        };
45518
45519        parsePat = function parsePat(payload, pat) {
45520          pat.section_number = payload[7]; // eslint-disable-line camelcase
45521
45522          pat.last_section_number = payload[8]; // eslint-disable-line camelcase
45523          // skip the PSI header and parse the first PMT entry
45524
45525          self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
45526          pat.pmtPid = self.pmtPid;
45527        };
45528        /**
45529         * Parse out the relevant fields of a Program Map Table (PMT).
45530         * @param payload {Uint8Array} the PMT-specific portion of an MP2T
45531         * packet. The first byte in this array should be the table_id
45532         * field.
45533         * @param pmt {object} the object that should be decorated with
45534         * fields parsed from the PMT.
45535         */
45536
45537
45538        parsePmt = function parsePmt(payload, pmt) {
45539          var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
45540          // take effect. We don't believe this should ever be the case
45541          // for HLS but we'll ignore "forward" PMT declarations if we see
45542          // them. Future PMT declarations have the current_next_indicator
45543          // set to zero.
45544
45545          if (!(payload[5] & 0x01)) {
45546            return;
45547          } // overwrite any existing program map table
45548
45549
45550          self.programMapTable = {
45551            video: null,
45552            audio: null,
45553            'timed-metadata': {}
45554          }; // the mapping table ends at the end of the current section
45555
45556          sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
45557          tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
45558          // long the program info descriptors are
45559
45560          programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
45561
45562          offset = 12 + programInfoLength;
45563
45564          while (offset < tableEnd) {
45565            var streamType = payload[offset];
45566            var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
45567            // TODO: should this be done for metadata too? for now maintain behavior of
45568            //       multiple metadata streams
45569
45570            if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
45571              self.programMapTable.video = pid;
45572            } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
45573              self.programMapTable.audio = pid;
45574            } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
45575              // map pid to stream type for metadata streams
45576              self.programMapTable['timed-metadata'][pid] = streamType;
45577            } // move to the next table entry
45578            // skip past the elementary stream descriptors, if present
45579
45580
45581            offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
45582          } // record the map on the packet as well
45583
45584
45585          pmt.programMapTable = self.programMapTable;
45586        };
45587        /**
45588         * Deliver a new MP2T packet to the next stream in the pipeline.
45589         */
45590
45591
45592        this.push = function (packet) {
45593          var result = {},
45594              offset = 4;
45595          result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
45596
45597          result.pid = packet[1] & 0x1f;
45598          result.pid <<= 8;
45599          result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
45600          // fifth byte of the TS packet header. The adaptation field is
45601          // used to add stuffing to PES packets that don't fill a complete
45602          // TS packet, and to specify some forms of timing and control data
45603          // that we do not currently use.
45604
45605          if ((packet[3] & 0x30) >>> 4 > 0x01) {
45606            offset += packet[offset] + 1;
45607          } // parse the rest of the packet based on the type
45608
45609
45610          if (result.pid === 0) {
45611            result.type = 'pat';
45612            parsePsi(packet.subarray(offset), result);
45613            this.trigger('data', result);
45614          } else if (result.pid === this.pmtPid) {
45615            result.type = 'pmt';
45616            parsePsi(packet.subarray(offset), result);
45617            this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
45618
45619            while (this.packetsWaitingForPmt.length) {
45620              this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
45621            }
45622          } else if (this.programMapTable === undefined) {
45623            // When we have not seen a PMT yet, defer further processing of
45624            // PES packets until one has been parsed
vendor: 9,362 bytes, lines 45625-45882
45625            this.packetsWaitingForPmt.push([packet, offset, result]);
45626          } else {
45627            this.processPes_(packet, offset, result);
45628          }
45629        };
45630
45631        this.processPes_ = function (packet, offset, result) {
45632          // set the appropriate stream type
45633          if (result.pid === this.programMapTable.video) {
45634            result.streamType = streamTypes.H264_STREAM_TYPE;
45635          } else if (result.pid === this.programMapTable.audio) {
45636            result.streamType = streamTypes.ADTS_STREAM_TYPE;
45637          } else {
45638            // if not video or audio, it is timed-metadata or unknown
45639            // if unknown, streamType will be undefined
45640            result.streamType = this.programMapTable['timed-metadata'][result.pid];
45641          }
45642
45643          result.type = 'pes';
45644          result.data = packet.subarray(offset);
45645          this.trigger('data', result);
45646        };
45647      };
45648
45649      _TransportParseStream.prototype = new stream();
45650      _TransportParseStream.STREAM_TYPES = {
45651        h264: 0x1b,
45652        adts: 0x0f
45653      };
45654      /**
45655       * Reconsistutes program elementary stream (PES) packets from parsed
45656       * transport stream packets. That is, if you pipe an
45657       * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
45658       * events will be events which capture the bytes for individual PES
45659       * packets plus relevant metadata that has been extracted from the
45660       * container.
45661       */
45662
45663      _ElementaryStream = function ElementaryStream() {
45664        var self = this,
45665            // PES packet fragments
45666        video = {
45667          data: [],
45668          size: 0
45669        },
45670            audio = {
45671          data: [],
45672          size: 0
45673        },
45674            timedMetadata = {
45675          data: [],
45676          size: 0
45677        },
45678            programMapTable,
45679            parsePes = function parsePes(payload, pes) {
45680          var ptsDtsFlags; // get the packet length, this will be 0 for video
45681
45682          pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
45683
45684          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
45685          // and a DTS value. Determine what combination of values is
45686          // available to work with.
45687
45688          ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
45689          // performs all bitwise operations on 32-bit integers but javascript
45690          // supports a much greater range (52-bits) of integer using standard
45691          // mathematical operations.
45692          // We construct a 31-bit value using bitwise operators over the 31
45693          // most significant bits and then multiply by 4 (equal to a left-shift
45694          // of 2) before we add the final 2 least significant bits of the
45695          // timestamp (equal to an OR.)
45696
45697          if (ptsDtsFlags & 0xC0) {
45698            // the PTS and DTS are not written out directly. For information
45699            // on how they are encoded, see
45700            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
45701            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
45702            pes.pts *= 4; // Left shift by 2
45703
45704            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
45705
45706            pes.dts = pes.pts;
45707
45708            if (ptsDtsFlags & 0x40) {
45709              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
45710              pes.dts *= 4; // Left shift by 2
45711
45712              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
45713            }
45714          } // the data section starts immediately after the PES header.
45715          // pes_header_data_length specifies the number of header bytes
45716          // that follow the last byte of the field.
45717
45718
45719          pes.data = payload.subarray(9 + payload[8]);
45720        },
45721
45722        /**
45723          * Pass completely parsed PES packets to the next stream in the pipeline
45724         **/
45725        flushStream = function flushStream(stream, type, forceFlush) {
45726          var packetData = new Uint8Array(stream.size),
45727              event = {
45728            type: type
45729          },
45730              i = 0,
45731              offset = 0,
45732              packetFlushable = false,
45733              fragment; // do nothing if there is not enough buffered data for a complete
45734          // PES header
45735
45736          if (!stream.data.length || stream.size < 9) {
45737            return;
45738          }
45739
45740          event.trackId = stream.data[0].pid; // reassemble the packet
45741
45742          for (i = 0; i < stream.data.length; i++) {
45743            fragment = stream.data[i];
45744            packetData.set(fragment.data, offset);
45745            offset += fragment.data.byteLength;
45746          } // parse assembled packet's PES header
45747
45748
45749          parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
45750          // check that there is enough stream data to fill the packet
45751
45752          packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
45753
45754          if (forceFlush || packetFlushable) {
45755            stream.size = 0;
45756            stream.data.length = 0;
45757          } // only emit packets that are complete. this is to avoid assembling
45758          // incomplete PES packets due to poor segmentation
45759
45760
45761          if (packetFlushable) {
45762            self.trigger('data', event);
45763          }
45764        };
45765
45766        _ElementaryStream.prototype.init.call(this);
45767        /**
45768         * Identifies M2TS packet types and parses PES packets using metadata
45769         * parsed from the PMT
45770         **/
45771
45772
45773        this.push = function (data) {
45774          ({
45775            pat: function pat() {// we have to wait for the PMT to arrive as well before we
45776              // have any meaningful metadata
45777            },
45778            pes: function pes() {
45779              var stream, streamType;
45780
45781              switch (data.streamType) {
45782                case streamTypes.H264_STREAM_TYPE:
45783                  stream = video;
45784                  streamType = 'video';
45785                  break;
45786
45787                case streamTypes.ADTS_STREAM_TYPE:
45788                  stream = audio;
45789                  streamType = 'audio';
45790                  break;
45791
45792                case streamTypes.METADATA_STREAM_TYPE:
45793                  stream = timedMetadata;
45794                  streamType = 'timed-metadata';
45795                  break;
45796
45797                default:
45798                  // ignore unknown stream types
45799                  return;
45800              } // if a new packet is starting, we can flush the completed
45801              // packet
45802
45803
45804              if (data.payloadUnitStartIndicator) {
45805                flushStream(stream, streamType, true);
45806              } // buffer this fragment until we are sure we've received the
45807              // complete payload
45808
45809
45810              stream.data.push(data);
45811              stream.size += data.data.byteLength;
45812            },
45813            pmt: function pmt() {
45814              var event = {
45815                type: 'metadata',
45816                tracks: []
45817              };
45818              programMapTable = data.programMapTable; // translate audio and video streams to tracks
45819
45820              if (programMapTable.video !== null) {
45821                event.tracks.push({
45822                  timelineStartInfo: {
45823                    baseMediaDecodeTime: 0
45824                  },
45825                  id: +programMapTable.video,
45826                  codec: 'avc',
45827                  type: 'video'
45828                });
45829              }
45830
45831              if (programMapTable.audio !== null) {
45832                event.tracks.push({
45833                  timelineStartInfo: {
45834                    baseMediaDecodeTime: 0
45835                  },
45836                  id: +programMapTable.audio,
45837                  codec: 'adts',
45838                  type: 'audio'
45839                });
45840              }
45841
45842              self.trigger('data', event);
45843            }
45844          })[data.type]();
45845        };
45846
45847        this.reset = function () {
45848          video.size = 0;
45849          video.data.length = 0;
45850          audio.size = 0;
45851          audio.data.length = 0;
45852          this.trigger('reset');
45853        };
45854        /**
45855         * Flush any remaining input. Video PES packets may be of variable
45856         * length. Normally, the start of a new video packet can trigger the
45857         * finalization of the previous packet. That is not possible if no
45858         * more video is forthcoming, however. In that case, some other
45859         * mechanism (like the end of the file) has to be employed. When it is
45860         * clear that no additional data is forthcoming, calling this method
45861         * will flush the buffered packets.
45862         */
45863
45864
45865        this.flushStreams_ = function () {
45866          // !!THIS ORDER IS IMPORTANT!!
45867          // video first then audio
45868          flushStream(video, 'video');
45869          flushStream(audio, 'audio');
45870          flushStream(timedMetadata, 'timed-metadata');
45871        };
45872
45873        this.flush = function () {
45874          this.flushStreams_();
45875          this.trigger('done');
45876        };
45877      };
45878
45879      _ElementaryStream.prototype = new stream();
45880      var m2ts = {
45881        PAT_PID: 0x0000,
45882        MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 5,940 bytes, lines 45883-46050
45883        TransportPacketStream: _TransportPacketStream,
45884        TransportParseStream: _TransportParseStream,
45885        ElementaryStream: _ElementaryStream,
45886        TimestampRolloverStream: TimestampRolloverStream$1,
45887        CaptionStream: captionStream.CaptionStream,
45888        Cea608Stream: captionStream.Cea608Stream,
45889        MetadataStream: metadataStream
45890      };
45891
45892      for (var type in streamTypes) {
45893        if (streamTypes.hasOwnProperty(type)) {
45894          m2ts[type] = streamTypes[type];
45895        }
45896      }
45897
45898      var m2ts_1 = m2ts;
45899      var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS;
45900
45901      var _AdtsStream;
45902
45903      var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
45904      /*
45905       * Accepts a ElementaryStream and emits data events with parsed
45906       * AAC Audio Frames of the individual packets. Input audio in ADTS
45907       * format is unpacked and re-emitted as AAC frames.
45908       *
45909       * @see http://wiki.multimedia.cx/index.php?title=ADTS
45910       * @see http://wiki.multimedia.cx/?title=Understanding_AAC
45911       */
45912
45913      _AdtsStream = function AdtsStream(handlePartialSegments) {
45914        var buffer,
45915            frameNum = 0;
45916
45917        _AdtsStream.prototype.init.call(this);
45918
45919        this.push = function (packet) {
45920          var i = 0,
45921              frameLength,
45922              protectionSkipBytes,
45923              frameEnd,
45924              oldBuffer,
45925              sampleCount,
45926              adtsFrameDuration;
45927
45928          if (!handlePartialSegments) {
45929            frameNum = 0;
45930          }
45931
45932          if (packet.type !== 'audio') {
45933            // ignore non-audio data
45934            return;
45935          } // Prepend any data in the buffer to the input data so that we can parse
45936          // aac frames the cross a PES packet boundary
45937
45938
45939          if (buffer) {
45940            oldBuffer = buffer;
45941            buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
45942            buffer.set(oldBuffer);
45943            buffer.set(packet.data, oldBuffer.byteLength);
45944          } else {
45945            buffer = packet.data;
45946          } // unpack any ADTS frames which have been fully received
45947          // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
45948
45949
45950          while (i + 5 < buffer.length) {
45951            // Look for the start of an ADTS header..
45952            if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
45953              // If a valid header was not found,  jump one forward and attempt to
45954              // find a valid ADTS header starting at the next byte
45955              i++;
45956              continue;
45957            } // The protection skip bit tells us if we have 2 bytes of CRC data at the
45958            // end of the ADTS header
45959
45960
45961            protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
45962            // end of the sync sequence
45963
45964            frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
45965            sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
45966            adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
45967            frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
45968            // and wait for more data
45969
45970            if (buffer.byteLength < frameEnd) {
45971              return;
45972            } // Otherwise, deliver the complete AAC frame
45973
45974
45975            this.trigger('data', {
45976              pts: packet.pts + frameNum * adtsFrameDuration,
45977              dts: packet.dts + frameNum * adtsFrameDuration,
45978              sampleCount: sampleCount,
45979              audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
45980              channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
45981              samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
45982              samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
45983              // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
45984              samplesize: 16,
45985              data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
45986            });
45987            frameNum++; // If the buffer is empty, clear it and return
45988
45989            if (buffer.byteLength === frameEnd) {
45990              buffer = undefined;
45991              return;
45992            } // Remove the finished frame from the buffer and start the process again
45993
45994
45995            buffer = buffer.subarray(frameEnd);
45996          }
45997        };
45998
45999        this.flush = function () {
46000          frameNum = 0;
46001          this.trigger('done');
46002        };
46003
46004        this.reset = function () {
46005          buffer = void 0;
46006          this.trigger('reset');
46007        };
46008
46009        this.endTimeline = function () {
46010          buffer = void 0;
46011          this.trigger('endedtimeline');
46012        };
46013      };
46014
46015      _AdtsStream.prototype = new stream();
46016      var adts = _AdtsStream;
46017      /**
46018       * mux.js
46019       *
46020       * Copyright (c) Brightcove
46021       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46022       */
46023
46024      var ExpGolomb;
46025      /**
46026       * Parser for exponential Golomb codes, a variable-bitwidth number encoding
46027       * scheme used by h264.
46028       */
46029
46030      ExpGolomb = function ExpGolomb(workingData) {
46031        var // the number of bytes left to examine in workingData
46032        workingBytesAvailable = workingData.byteLength,
46033            // the current word being examined
46034        workingWord = 0,
46035            // :uint
46036        // the number of bits left to examine in the current word
46037        workingBitsAvailable = 0; // :uint;
46038        // ():uint
46039
46040        this.length = function () {
46041          return 8 * workingBytesAvailable;
46042        }; // ():uint
46043
46044
46045        this.bitsAvailable = function () {
46046          return 8 * workingBytesAvailable + workingBitsAvailable;
46047        }; // ():void
46048
46049
46050        this.loadWord = function () {
vendor: 8,126 bytes, lines 46051-46328
46051          var position = workingData.byteLength - workingBytesAvailable,
46052              workingBytes = new Uint8Array(4),
46053              availableBytes = Math.min(4, workingBytesAvailable);
46054
46055          if (availableBytes === 0) {
46056            throw new Error('no bytes available');
46057          }
46058
46059          workingBytes.set(workingData.subarray(position, position + availableBytes));
46060          workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
46061
46062          workingBitsAvailable = availableBytes * 8;
46063          workingBytesAvailable -= availableBytes;
46064        }; // (count:int):void
46065
46066
46067        this.skipBits = function (count) {
46068          var skipBytes; // :int
46069
46070          if (workingBitsAvailable > count) {
46071            workingWord <<= count;
46072            workingBitsAvailable -= count;
46073          } else {
46074            count -= workingBitsAvailable;
46075            skipBytes = Math.floor(count / 8);
46076            count -= skipBytes * 8;
46077            workingBytesAvailable -= skipBytes;
46078            this.loadWord();
46079            workingWord <<= count;
46080            workingBitsAvailable -= count;
46081          }
46082        }; // (size:int):uint
46083
46084
46085        this.readBits = function (size) {
46086          var bits = Math.min(workingBitsAvailable, size),
46087              // :uint
46088          valu = workingWord >>> 32 - bits; // :uint
46089          // if size > 31, handle error
46090
46091          workingBitsAvailable -= bits;
46092
46093          if (workingBitsAvailable > 0) {
46094            workingWord <<= bits;
46095          } else if (workingBytesAvailable > 0) {
46096            this.loadWord();
46097          }
46098
46099          bits = size - bits;
46100
46101          if (bits > 0) {
46102            return valu << bits | this.readBits(bits);
46103          }
46104
46105          return valu;
46106        }; // ():uint
46107
46108
46109        this.skipLeadingZeros = function () {
46110          var leadingZeroCount; // :uint
46111
46112          for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
46113            if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
46114              // the first bit of working word is 1
46115              workingWord <<= leadingZeroCount;
46116              workingBitsAvailable -= leadingZeroCount;
46117              return leadingZeroCount;
46118            }
46119          } // we exhausted workingWord and still have not found a 1
46120
46121
46122          this.loadWord();
46123          return leadingZeroCount + this.skipLeadingZeros();
46124        }; // ():void
46125
46126
46127        this.skipUnsignedExpGolomb = function () {
46128          this.skipBits(1 + this.skipLeadingZeros());
46129        }; // ():void
46130
46131
46132        this.skipExpGolomb = function () {
46133          this.skipBits(1 + this.skipLeadingZeros());
46134        }; // ():uint
46135
46136
46137        this.readUnsignedExpGolomb = function () {
46138          var clz = this.skipLeadingZeros(); // :uint
46139
46140          return this.readBits(clz + 1) - 1;
46141        }; // ():int
46142
46143
46144        this.readExpGolomb = function () {
46145          var valu = this.readUnsignedExpGolomb(); // :int
46146
46147          if (0x01 & valu) {
46148            // the number is odd if the low order bit is set
46149            return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
46150          }
46151
46152          return -1 * (valu >>> 1); // divide by two then make it negative
46153        }; // Some convenience functions
46154        // :Boolean
46155
46156
46157        this.readBoolean = function () {
46158          return this.readBits(1) === 1;
46159        }; // ():int
46160
46161
46162        this.readUnsignedByte = function () {
46163          return this.readBits(8);
46164        };
46165
46166        this.loadWord();
46167      };
46168
46169      var expGolomb = ExpGolomb;
46170
46171      var _H264Stream, _NalByteStream;
46172
46173      var PROFILES_WITH_OPTIONAL_SPS_DATA;
46174      /**
46175       * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
46176       */
46177
46178      _NalByteStream = function NalByteStream() {
46179        var syncPoint = 0,
46180            i,
46181            buffer;
46182
46183        _NalByteStream.prototype.init.call(this);
46184        /*
46185         * Scans a byte stream and triggers a data event with the NAL units found.
46186         * @param {Object} data Event received from H264Stream
46187         * @param {Uint8Array} data.data The h264 byte stream to be scanned
46188         *
46189         * @see H264Stream.push
46190         */
46191
46192
46193        this.push = function (data) {
46194          var swapBuffer;
46195
46196          if (!buffer) {
46197            buffer = data.data;
46198          } else {
46199            swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
46200            swapBuffer.set(buffer);
46201            swapBuffer.set(data.data, buffer.byteLength);
46202            buffer = swapBuffer;
46203          }
46204
46205          var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
46206          // scan for NAL unit boundaries
46207          // a match looks like this:
46208          // 0 0 1 .. NAL .. 0 0 1
46209          // ^ sync point        ^ i
46210          // or this:
46211          // 0 0 1 .. NAL .. 0 0 0
46212          // ^ sync point        ^ i
46213          // advance the sync point to a NAL start, if necessary
46214
46215          for (; syncPoint < len - 3; syncPoint++) {
46216            if (buffer[syncPoint + 2] === 1) {
46217              // the sync point is properly aligned
46218              i = syncPoint + 5;
46219              break;
46220            }
46221          }
46222
46223          while (i < len) {
46224            // look at the current byte to determine if we've hit the end of
46225            // a NAL unit boundary
46226            switch (buffer[i]) {
46227              case 0:
46228                // skip past non-sync sequences
46229                if (buffer[i - 1] !== 0) {
46230                  i += 2;
46231                  break;
46232                } else if (buffer[i - 2] !== 0) {
46233                  i++;
46234                  break;
46235                } // deliver the NAL unit if it isn't empty
46236
46237
46238                if (syncPoint + 3 !== i - 2) {
46239                  this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
46240                } // drop trailing zeroes
46241
46242
46243                do {
46244                  i++;
46245                } while (buffer[i] !== 1 && i < len);
46246
46247                syncPoint = i - 2;
46248                i += 3;
46249                break;
46250
46251              case 1:
46252                // skip past non-sync sequences
46253                if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
46254                  i += 3;
46255                  break;
46256                } // deliver the NAL unit
46257
46258
46259                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
46260                syncPoint = i - 2;
46261                i += 3;
46262                break;
46263
46264              default:
46265                // the current byte isn't a one or zero, so it cannot be part
46266                // of a sync sequence
46267                i += 3;
46268                break;
46269            }
46270          } // filter out the NAL units that were delivered
46271
46272
46273          buffer = buffer.subarray(syncPoint);
46274          i -= syncPoint;
46275          syncPoint = 0;
46276        };
46277
46278        this.reset = function () {
46279          buffer = null;
46280          syncPoint = 0;
46281          this.trigger('reset');
46282        };
46283
46284        this.flush = function () {
46285          // deliver the last buffered NAL unit
46286          if (buffer && buffer.byteLength > 3) {
46287            this.trigger('data', buffer.subarray(syncPoint + 3));
46288          } // reset the stream state
46289
46290
46291          buffer = null;
46292          syncPoint = 0;
46293          this.trigger('done');
46294        };
46295
46296        this.endTimeline = function () {
46297          this.flush();
46298          this.trigger('endedtimeline');
46299        };
46300      };
46301
46302      _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
46303      // see Recommendation ITU-T H.264 (4/2013),
46304      // 7.3.2.1.1 Sequence parameter set data syntax
46305
46306      PROFILES_WITH_OPTIONAL_SPS_DATA = {
46307        100: true,
46308        110: true,
46309        122: true,
46310        244: true,
46311        44: true,
46312        83: true,
46313        86: true,
46314        118: true,
46315        128: true,
46316        138: true,
46317        139: true,
46318        134: true
46319      };
46320      /**
46321       * Accepts input from a ElementaryStream and produces H.264 NAL unit data
46322       * events.
46323       */
46324
46325      _H264Stream = function H264Stream() {
46326        var nalByteStream = new _NalByteStream(),
46327            self,
46328            trackId,
46329            currentPts,
46330            currentDts,
46331            discardEmulationPreventionBytes,
46332            readSequenceParameterSet,
46333            skipScalingList;
46334
46335        _H264Stream.prototype.init.call(this);
46336
46337        self = this;
46338        /*
46339         * Pushes a packet from a stream onto the NalByteStream
46340         *
46341         * @param {Object} packet - A packet received from a stream
46342         * @param {Uint8Array} packet.data - The raw bytes of the packet
46343         * @param {Number} packet.dts - Decode timestamp of the packet
46344         * @param {Number} packet.pts - Presentation timestamp of the packet
46345         * @param {Number} packet.trackId - The id of the h264 track this packet came from
46346         * @param {('video'|'audio')} packet.type - The type of packet
46347         *
46348         */
46349
46350        this.push = function (packet) {
46351          if (packet.type !== 'video') {
46352            return;
46353          }
46354
46355          trackId = packet.trackId;
46356          currentPts = packet.pts;
46357          currentDts = packet.dts;
46358          nalByteStream.push(packet);
46359        };
46360        /*
46361         * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
46362         * for the NALUs to the next stream component.
46363         * Also, preprocess caption and sequence parameter NALUs.
46364         *
46365         * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
46366         * @see NalByteStream.push
46367         */
46368
46369
46370        nalByteStream.on('data', function (data) {
46371          var event = {
46372            trackId: trackId,
vendor: 16,384 bytes, lines 46373-46849
46373            pts: currentPts,
46374            dts: currentDts,
46375            data: data
46376          };
46377
46378          switch (data[0] & 0x1f) {
46379            case 0x05:
46380              event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
46381              break;
46382
46383            case 0x06:
46384              event.nalUnitType = 'sei_rbsp';
46385              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
46386              break;
46387
46388            case 0x07:
46389              event.nalUnitType = 'seq_parameter_set_rbsp';
46390              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
46391              event.config = readSequenceParameterSet(event.escapedRBSP);
46392              break;
46393
46394            case 0x08:
46395              event.nalUnitType = 'pic_parameter_set_rbsp';
46396              break;
46397
46398            case 0x09:
46399              event.nalUnitType = 'access_unit_delimiter_rbsp';
46400              break;
46401          } // This triggers data on the H264Stream
46402
46403
46404          self.trigger('data', event);
46405        });
46406        nalByteStream.on('done', function () {
46407          self.trigger('done');
46408        });
46409        nalByteStream.on('partialdone', function () {
46410          self.trigger('partialdone');
46411        });
46412        nalByteStream.on('reset', function () {
46413          self.trigger('reset');
46414        });
46415        nalByteStream.on('endedtimeline', function () {
46416          self.trigger('endedtimeline');
46417        });
46418
46419        this.flush = function () {
46420          nalByteStream.flush();
46421        };
46422
46423        this.partialFlush = function () {
46424          nalByteStream.partialFlush();
46425        };
46426
46427        this.reset = function () {
46428          nalByteStream.reset();
46429        };
46430
46431        this.endTimeline = function () {
46432          nalByteStream.endTimeline();
46433        };
46434        /**
46435         * Advance the ExpGolomb decoder past a scaling list. The scaling
46436         * list is optionally transmitted as part of a sequence parameter
46437         * set and is not relevant to transmuxing.
46438         * @param count {number} the number of entries in this scaling list
46439         * @param expGolombDecoder {object} an ExpGolomb pointed to the
46440         * start of a scaling list
46441         * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
46442         */
46443
46444
46445        skipScalingList = function skipScalingList(count, expGolombDecoder) {
46446          var lastScale = 8,
46447              nextScale = 8,
46448              j,
46449              deltaScale;
46450
46451          for (j = 0; j < count; j++) {
46452            if (nextScale !== 0) {
46453              deltaScale = expGolombDecoder.readExpGolomb();
46454              nextScale = (lastScale + deltaScale + 256) % 256;
46455            }
46456
46457            lastScale = nextScale === 0 ? lastScale : nextScale;
46458          }
46459        };
46460        /**
46461         * Expunge any "Emulation Prevention" bytes from a "Raw Byte
46462         * Sequence Payload"
46463         * @param data {Uint8Array} the bytes of a RBSP from a NAL
46464         * unit
46465         * @return {Uint8Array} the RBSP without any Emulation
46466         * Prevention Bytes
46467         */
46468
46469
46470        discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
46471          var length = data.byteLength,
46472              emulationPreventionBytesPositions = [],
46473              i = 1,
46474              newLength,
46475              newData; // Find all `Emulation Prevention Bytes`
46476
46477          while (i < length - 2) {
46478            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
46479              emulationPreventionBytesPositions.push(i + 2);
46480              i += 2;
46481            } else {
46482              i++;
46483            }
46484          } // If no Emulation Prevention Bytes were found just return the original
46485          // array
46486
46487
46488          if (emulationPreventionBytesPositions.length === 0) {
46489            return data;
46490          } // Create a new array to hold the NAL unit data
46491
46492
46493          newLength = length - emulationPreventionBytesPositions.length;
46494          newData = new Uint8Array(newLength);
46495          var sourceIndex = 0;
46496
46497          for (i = 0; i < newLength; sourceIndex++, i++) {
46498            if (sourceIndex === emulationPreventionBytesPositions[0]) {
46499              // Skip this byte
46500              sourceIndex++; // Remove this position index
46501
46502              emulationPreventionBytesPositions.shift();
46503            }
46504
46505            newData[i] = data[sourceIndex];
46506          }
46507
46508          return newData;
46509        };
46510        /**
46511         * Read a sequence parameter set and return some interesting video
46512         * properties. A sequence parameter set is the H264 metadata that
46513         * describes the properties of upcoming video frames.
46514         * @param data {Uint8Array} the bytes of a sequence parameter set
46515         * @return {object} an object with configuration parsed from the
46516         * sequence parameter set, including the dimensions of the
46517         * associated video frames.
46518         */
46519
46520
46521        readSequenceParameterSet = function readSequenceParameterSet(data) {
46522          var frameCropLeftOffset = 0,
46523              frameCropRightOffset = 0,
46524              frameCropTopOffset = 0,
46525              frameCropBottomOffset = 0,
46526              sarScale = 1,
46527              expGolombDecoder,
46528              profileIdc,
46529              levelIdc,
46530              profileCompatibility,
46531              chromaFormatIdc,
46532              picOrderCntType,
46533              numRefFramesInPicOrderCntCycle,
46534              picWidthInMbsMinus1,
46535              picHeightInMapUnitsMinus1,
46536              frameMbsOnlyFlag,
46537              scalingListCount,
46538              sarRatio,
46539              aspectRatioIdc,
46540              i;
46541          expGolombDecoder = new expGolomb(data);
46542          profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
46543
46544          profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
46545
46546          levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
46547
46548          expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
46549          // some profiles have more optional data we don't need
46550
46551          if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
46552            chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
46553
46554            if (chromaFormatIdc === 3) {
46555              expGolombDecoder.skipBits(1); // separate_colour_plane_flag
46556            }
46557
46558            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
46559
46560            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
46561
46562            expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
46563
46564            if (expGolombDecoder.readBoolean()) {
46565              // seq_scaling_matrix_present_flag
46566              scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
46567
46568              for (i = 0; i < scalingListCount; i++) {
46569                if (expGolombDecoder.readBoolean()) {
46570                  // seq_scaling_list_present_flag[ i ]
46571                  if (i < 6) {
46572                    skipScalingList(16, expGolombDecoder);
46573                  } else {
46574                    skipScalingList(64, expGolombDecoder);
46575                  }
46576                }
46577              }
46578            }
46579          }
46580
46581          expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
46582
46583          picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
46584
46585          if (picOrderCntType === 0) {
46586            expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
46587          } else if (picOrderCntType === 1) {
46588            expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
46589
46590            expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
46591
46592            expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
46593
46594            numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
46595
46596            for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
46597              expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
46598            }
46599          }
46600
46601          expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
46602
46603          expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
46604
46605          picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
46606          picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
46607          frameMbsOnlyFlag = expGolombDecoder.readBits(1);
46608
46609          if (frameMbsOnlyFlag === 0) {
46610            expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
46611          }
46612
46613          expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
46614
46615          if (expGolombDecoder.readBoolean()) {
46616            // frame_cropping_flag
46617            frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
46618            frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
46619            frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
46620            frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
46621          }
46622
46623          if (expGolombDecoder.readBoolean()) {
46624            // vui_parameters_present_flag
46625            if (expGolombDecoder.readBoolean()) {
46626              // aspect_ratio_info_present_flag
46627              aspectRatioIdc = expGolombDecoder.readUnsignedByte();
46628
46629              switch (aspectRatioIdc) {
46630                case 1:
46631                  sarRatio = [1, 1];
46632                  break;
46633
46634                case 2:
46635                  sarRatio = [12, 11];
46636                  break;
46637
46638                case 3:
46639                  sarRatio = [10, 11];
46640                  break;
46641
46642                case 4:
46643                  sarRatio = [16, 11];
46644                  break;
46645
46646                case 5:
46647                  sarRatio = [40, 33];
46648                  break;
46649
46650                case 6:
46651                  sarRatio = [24, 11];
46652                  break;
46653
46654                case 7:
46655                  sarRatio = [20, 11];
46656                  break;
46657
46658                case 8:
46659                  sarRatio = [32, 11];
46660                  break;
46661
46662                case 9:
46663                  sarRatio = [80, 33];
46664                  break;
46665
46666                case 10:
46667                  sarRatio = [18, 11];
46668                  break;
46669
46670                case 11:
46671                  sarRatio = [15, 11];
46672                  break;
46673
46674                case 12:
46675                  sarRatio = [64, 33];
46676                  break;
46677
46678                case 13:
46679                  sarRatio = [160, 99];
46680                  break;
46681
46682                case 14:
46683                  sarRatio = [4, 3];
46684                  break;
46685
46686                case 15:
46687                  sarRatio = [3, 2];
46688                  break;
46689
46690                case 16:
46691                  sarRatio = [2, 1];
46692                  break;
46693
46694                case 255:
46695                  {
46696                    sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
46697                    break;
46698                  }
46699              }
46700
46701              if (sarRatio) {
46702                sarScale = sarRatio[0] / sarRatio[1];
46703              }
46704            }
46705          }
46706
46707          return {
46708            profileIdc: profileIdc,
46709            levelIdc: levelIdc,
46710            profileCompatibility: profileCompatibility,
46711            width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
46712            height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
46713            sarRatio: sarRatio
46714          };
46715        };
46716      };
46717
46718      _H264Stream.prototype = new stream();
46719      var h264 = {
46720        H264Stream: _H264Stream,
46721        NalByteStream: _NalByteStream
46722      };
46723      /**
46724       * mux.js
46725       *
46726       * Copyright (c) Brightcove
46727       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46728       *
46729       * Utilities to detect basic properties and metadata about Aac data.
46730       */
46731
46732      var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
46733
46734      var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
46735        var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
46736            flags = header[byteIndex + 5],
46737            footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
46738
46739        returnSize = returnSize >= 0 ? returnSize : 0;
46740
46741        if (footerPresent) {
46742          return returnSize + 20;
46743        }
46744
46745        return returnSize + 10;
46746      };
46747
46748      var getId3Offset = function getId3Offset(data, offset) {
46749        if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
46750          return offset;
46751        }
46752
46753        offset += parseId3TagSize(data, offset);
46754        return getId3Offset(data, offset);
46755      }; // TODO: use vhs-utils
46756
46757
46758      var isLikelyAacData = function isLikelyAacData(data) {
46759        var offset = getId3Offset(data, 0);
46760        return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this
46761        // is not mp3 data but aac data.
46762        (data[offset + 1] & 0x16) === 0x10;
46763      };
46764
46765      var parseSyncSafeInteger$1 = function parseSyncSafeInteger$1(data) {
46766        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
46767      }; // return a percent-encoded representation of the specified byte range
46768      // @see http://en.wikipedia.org/wiki/Percent-encoding
46769
46770
46771      var percentEncode$1 = function percentEncode$1(bytes, start, end) {
46772        var i,
46773            result = '';
46774
46775        for (i = start; i < end; i++) {
46776          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
46777        }
46778
46779        return result;
46780      }; // return the string representation of the specified byte range,
46781      // interpreted as ISO-8859-1.
46782
46783
46784      var parseIso88591$1 = function parseIso88591$1(bytes, start, end) {
46785        return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
46786      };
46787
46788      var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
46789        var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
46790            middle = header[byteIndex + 4] << 3,
46791            highTwo = header[byteIndex + 3] & 0x3 << 11;
46792        return highTwo | middle | lowThree;
46793      };
46794
46795      var parseType = function parseType(header, byteIndex) {
46796        if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
46797          return 'timed-metadata';
46798        } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
46799          return 'audio';
46800        }
46801
46802        return null;
46803      };
46804
46805      var parseSampleRate = function parseSampleRate(packet) {
46806        var i = 0;
46807
46808        while (i + 5 < packet.length) {
46809          if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
46810            // If a valid header was not found,  jump one forward and attempt to
46811            // find a valid ADTS header starting at the next byte
46812            i++;
46813            continue;
46814          }
46815
46816          return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
46817        }
46818
46819        return null;
46820      };
46821
46822      var parseAacTimestamp = function parseAacTimestamp(packet) {
46823        var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
46824
46825        frameStart = 10;
46826
46827        if (packet[5] & 0x40) {
46828          // advance the frame start past the extended header
46829          frameStart += 4; // header size field
46830
46831          frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
46832        } // parse one or more ID3 frames
46833        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46834
46835
46836        do {
46837          // determine the number of bytes in this frame
46838          frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
46839
46840          if (frameSize < 1) {
46841            return null;
46842          }
46843
46844          frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
46845
46846          if (frameHeader === 'PRIV') {
46847            frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
46848
46849            for (var i = 0; i < 
vendor: 4,724 bytes, lines 46849-46992
46849frame.byteLength; i++) {
46850              if (frame[i] === 0) {
46851                var owner = parseIso88591$1(frame, 0, i);
46852
46853                if (owner === 'com.apple.streaming.transportStreamTimestamp') {
46854                  var d = frame.subarray(i + 1);
46855                  var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
46856                  size *= 4;
46857                  size += d[7] & 0x03;
46858                  return size;
46859                }
46860
46861                break;
46862              }
46863            }
46864          }
46865
46866          frameStart += 10; // advance past the frame header
46867
46868          frameStart += frameSize; // advance past the frame body
46869        } while (frameStart < packet.byteLength);
46870
46871        return null;
46872      };
46873
46874      var utils = {
46875        isLikelyAacData: isLikelyAacData,
46876        parseId3TagSize: parseId3TagSize,
46877        parseAdtsSize: parseAdtsSize,
46878        parseType: parseType,
46879        parseSampleRate: parseSampleRate,
46880        parseAacTimestamp: parseAacTimestamp
46881      }; // Constants
46882
46883      var _AacStream;
46884      /**
46885       * Splits an incoming stream of binary data into ADTS and ID3 Frames.
46886       */
46887
46888
46889      _AacStream = function AacStream() {
46890        var everything = new Uint8Array(),
46891            timeStamp = 0;
46892
46893        _AacStream.prototype.init.call(this);
46894
46895        this.setTimestamp = function (timestamp) {
46896          timeStamp = timestamp;
46897        };
46898
46899        this.push = function (bytes) {
46900          var frameSize = 0,
46901              byteIndex = 0,
46902              bytesLeft,
46903              chunk,
46904              packet,
46905              tempLength; // If there are bytes remaining from the last segment, prepend them to the
46906          // bytes that were pushed in
46907
46908          if (everything.length) {
46909            tempLength = everything.length;
46910            everything = new Uint8Array(bytes.byteLength + tempLength);
46911            everything.set(everything.subarray(0, tempLength));
46912            everything.set(bytes, tempLength);
46913          } else {
46914            everything = bytes;
46915          }
46916
46917          while (everything.length - byteIndex >= 3) {
46918            if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
46919              // Exit early because we don't have enough to parse
46920              // the ID3 tag header
46921              if (everything.length - byteIndex < 10) {
46922                break;
46923              } // check framesize
46924
46925
46926              frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
46927              // to emit a full packet
46928              // Add to byteIndex to support multiple ID3 tags in sequence
46929
46930              if (byteIndex + frameSize > everything.length) {
46931                break;
46932              }
46933
46934              chunk = {
46935                type: 'timed-metadata',
46936                data: everything.subarray(byteIndex, byteIndex + frameSize)
46937              };
46938              this.trigger('data', chunk);
46939              byteIndex += frameSize;
46940              continue;
46941            } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
46942              // Exit early because we don't have enough to parse
46943              // the ADTS frame header
46944              if (everything.length - byteIndex < 7) {
46945                break;
46946              }
46947
46948              frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
46949              // to emit a full packet
46950
46951              if (byteIndex + frameSize > everything.length) {
46952                break;
46953              }
46954
46955              packet = {
46956                type: 'audio',
46957                data: everything.subarray(byteIndex, byteIndex + frameSize),
46958                pts: timeStamp,
46959                dts: timeStamp
46960              };
46961              this.trigger('data', packet);
46962              byteIndex += frameSize;
46963              continue;
46964            }
46965
46966            byteIndex++;
46967          }
46968
46969          bytesLeft = everything.length - byteIndex;
46970
46971          if (bytesLeft > 0) {
46972            everything = everything.subarray(byteIndex);
46973          } else {
46974            everything = new Uint8Array();
46975          }
46976        };
46977
46978        this.reset = function () {
46979          everything = new Uint8Array();
46980          this.trigger('reset');
46981        };
46982
46983        this.endTimeline = function () {
46984          everything = new Uint8Array();
46985          this.trigger('endedtimeline');
46986        };
46987      };
46988
46989      _AacStream.prototype = new stream();
46990      var aac = _AacStream; // constants
46991
46992      var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
vendor: 7,111 bytes, lines 46993-47170
46993      var audioProperties = AUDIO_PROPERTIES;
46994      var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
46995      var videoProperties = VIDEO_PROPERTIES;
46996      var H264Stream$1 = h264.H264Stream;
46997      var isLikelyAacData$1 = utils.isLikelyAacData;
46998      var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // object types
46999
47000      var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
47001      /**
47002       * Compare two arrays (even typed) for same-ness
47003       */
47004
47005
47006      var arrayEquals = function arrayEquals(a, b) {
47007        var i;
47008
47009        if (a.length !== b.length) {
47010          return false;
47011        } // compare the value of each element in the array
47012
47013
47014        for (i = 0; i < a.length; i++) {
47015          if (a[i] !== b[i]) {
47016            return false;
47017          }
47018        }
47019
47020        return true;
47021      };
47022
47023      var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
47024        var ptsOffsetFromDts = startPts - startDts,
47025            decodeDuration = endDts - startDts,
47026            presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
47027        // however, the player time values will reflect a start from the baseMediaDecodeTime.
47028        // In order to provide relevant values for the player times, base timing info on the
47029        // baseMediaDecodeTime and the DTS and PTS durations of the segment.
47030
47031        return {
47032          start: {
47033            dts: baseMediaDecodeTime,
47034            pts: baseMediaDecodeTime + ptsOffsetFromDts
47035          },
47036          end: {
47037            dts: baseMediaDecodeTime + decodeDuration,
47038            pts: baseMediaDecodeTime + presentationDuration
47039          },
47040          prependedContentDuration: prependedContentDuration,
47041          baseMediaDecodeTime: baseMediaDecodeTime
47042        };
47043      };
47044      /**
47045       * Constructs a single-track, ISO BMFF media segment from AAC data
47046       * events. The output of this stream can be fed to a SourceBuffer
47047       * configured with a suitable initialization segment.
47048       * @param track {object} track metadata configuration
47049       * @param options {object} transmuxer options object
47050       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
47051       *        in the source; false to adjust the first segment to start at 0.
47052       */
47053
47054
47055      _AudioSegmentStream = function AudioSegmentStream(track, options) {
47056        var adtsFrames = [],
47057            sequenceNumber = 0,
47058            earliestAllowedDts = 0,
47059            audioAppendStartTs = 0,
47060            videoBaseMediaDecodeTime = Infinity;
47061        options = options || {};
47062
47063        _AudioSegmentStream.prototype.init.call(this);
47064
47065        this.push = function (data) {
47066          trackDecodeInfo.collectDtsInfo(track, data);
47067
47068          if (track) {
47069            audioProperties.forEach(function (prop) {
47070              track[prop] = data[prop];
47071            });
47072          } // buffer audio data until end() is called
47073
47074
47075          adtsFrames.push(data);
47076        };
47077
47078        this.setEarliestDts = function (earliestDts) {
47079          earliestAllowedDts = earliestDts;
47080        };
47081
47082        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
47083          videoBaseMediaDecodeTime = baseMediaDecodeTime;
47084        };
47085
47086        this.setAudioAppendStart = function (timestamp) {
47087          audioAppendStartTs = timestamp;
47088        };
47089
47090        this.flush = function () {
47091          var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed
47092
47093          if (adtsFrames.length === 0) {
47094            this.trigger('done', 'AudioSegmentStream');
47095            return;
47096          }
47097
47098          frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
47099          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
47100          audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
47101          // samples (that is, adts frames) in the audio data
47102
47103          track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
47104
47105          mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
47106          adtsFrames = [];
47107          moof = mp4Generator.moof(sequenceNumber, [track]);
47108          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
47109
47110          sequenceNumber++;
47111          boxes.set(moof);
47112          boxes.set(mdat, moof.byteLength);
47113          trackDecodeInfo.clearDtsInfo(track);
47114          frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
47115          // tests) on adding the timingInfo event. However, it seems unlikely that there's a
47116          // valid use-case where an init segment/data should be triggered without associated
47117          // frames. Leaving for now, but should be looked into.
47118
47119          if (frames.length) {
47120            this.trigger('timingInfo', {
47121              start: frames[0].pts,
47122              end: frames[0].pts + frames.length * frameDuration
47123            });
47124          }
47125
47126          this.trigger('data', {
47127            track: track,
47128            boxes: boxes
47129          });
47130          this.trigger('done', 'AudioSegmentStream');
47131        };
47132
47133        this.reset = function () {
47134          trackDecodeInfo.clearDtsInfo(track);
47135          adtsFrames = [];
47136          this.trigger('reset');
47137        };
47138      };
47139
47140      _AudioSegmentStream.prototype = new stream();
47141      /**
47142       * Constructs a single-track, ISO BMFF media segment from H264 data
47143       * events. The output of this stream can be fed to a SourceBuffer
47144       * configured with a suitable initialization segment.
47145       * @param track {object} track metadata configuration
47146       * @param options {object} transmuxer options object
47147       * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
47148       *        gopsToAlignWith list when attempting to align gop pts
47149       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
47150       *        in the source; false to adjust the first segment to start at 0.
47151       */
47152
47153      _VideoSegmentStream = function VideoSegmentStream(track, options) {
47154        var sequenceNumber = 0,
47155            nalUnits = [],
47156            gopsToAlignWith = [],
47157            config,
47158            pps;
47159        options = options || {};
47160
47161        _VideoSegmentStream.prototype.init.call(this);
47162
47163        delete track.minPTS;
47164        this.gopCache_ = [];
47165        /**
47166          * Constructs a ISO BMFF segment given H264 nalUnits
47167          * @param {Object} nalUnit A data event representing a nalUnit
47168          * @param {String} nalUnit.nalUnitType
47169          * @param {Object} nalUnit.config Properties for a mp4 track
47170          * @param {Uint8Array}
vendor: 11,982 bytes, lines 47170-47476
47170 nalUnit.data The nalUnit bytes
47171          * @see lib/codecs/h264.js
47172         **/
47173
47174        this.push = function (nalUnit) {
47175          trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
47176
47177          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
47178            config = nalUnit.config;
47179            track.sps = [nalUnit.data];
47180            videoProperties.forEach(function (prop) {
47181              track[prop] = config[prop];
47182            }, this);
47183          }
47184
47185          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
47186            pps = nalUnit.data;
47187            track.pps = [nalUnit.data];
47188          } // buffer video until flush() is called
47189
47190
47191          nalUnits.push(nalUnit);
47192        };
47193        /**
47194          * Pass constructed ISO BMFF track and boxes on to the
47195          * next stream in the pipeline
47196         **/
47197
47198
47199        this.flush = function () {
47200          var frames,
47201              gopForFusion,
47202              gops,
47203              moof,
47204              mdat,
47205              boxes,
47206              prependedContentDuration = 0,
47207              firstGop,
47208              lastGop; // Throw away nalUnits at the start of the byte stream until
47209          // we find the first AUD
47210
47211          while (nalUnits.length) {
47212            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
47213              break;
47214            }
47215
47216            nalUnits.shift();
47217          } // Return early if no video data has been observed
47218
47219
47220          if (nalUnits.length === 0) {
47221            this.resetStream_();
47222            this.trigger('done', 'VideoSegmentStream');
47223            return;
47224          } // Organize the raw nal-units into arrays that represent
47225          // higher-level constructs such as frames and gops
47226          // (group-of-pictures)
47227
47228
47229          frames = frameUtils.groupNalsIntoFrames(nalUnits);
47230          gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
47231          // a problem since MSE (on Chrome) requires a leading keyframe.
47232          //
47233          // We have two approaches to repairing this situation:
47234          // 1) GOP-FUSION:
47235          //    This is where we keep track of the GOPS (group-of-pictures)
47236          //    from previous fragments and attempt to find one that we can
47237          //    prepend to the current fragment in order to create a valid
47238          //    fragment.
47239          // 2) KEYFRAME-PULLING:
47240          //    Here we search for the first keyframe in the fragment and
47241          //    throw away all the frames between the start of the fragment
47242          //    and that keyframe. We then extend the duration and pull the
47243          //    PTS of the keyframe forward so that it covers the time range
47244          //    of the frames that were disposed of.
47245          //
47246          // #1 is far prefereable over #2 which can cause "stuttering" but
47247          // requires more things to be just right.
47248
47249          if (!gops[0][0].keyFrame) {
47250            // Search for a gop for fusion from our gopCache
47251            gopForFusion = this.getGopForFusion_(nalUnits[0], track);
47252
47253            if (gopForFusion) {
47254              // in order to provide more accurate timing information about the segment, save
47255              // the number of seconds prepended to the original segment due to GOP fusion
47256              prependedContentDuration = gopForFusion.duration;
47257              gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
47258              // new gop at the beginning
47259
47260              gops.byteLength += gopForFusion.byteLength;
47261              gops.nalCount += gopForFusion.nalCount;
47262              gops.pts = gopForFusion.pts;
47263              gops.dts = gopForFusion.dts;
47264              gops.duration += gopForFusion.duration;
47265            } else {
47266              // If we didn't find a candidate gop fall back to keyframe-pulling
47267              gops = frameUtils.extendFirstKeyFrame(gops);
47268            }
47269          } // Trim gops to align with gopsToAlignWith
47270
47271
47272          if (gopsToAlignWith.length) {
47273            var alignedGops;
47274
47275            if (options.alignGopsAtEnd) {
47276              alignedGops = this.alignGopsAtEnd_(gops);
47277            } else {
47278              alignedGops = this.alignGopsAtStart_(gops);
47279            }
47280
47281            if (!alignedGops) {
47282              // save all the nals in the last GOP into the gop cache
47283              this.gopCache_.unshift({
47284                gop: gops.pop(),
47285                pps: track.pps,
47286                sps: track.sps
47287              }); // Keep a maximum of 6 GOPs in the cache
47288
47289              this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
47290
47291              nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
47292
47293              this.resetStream_();
47294              this.trigger('done', 'VideoSegmentStream');
47295              return;
47296            } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
47297            // when recalculated before sending off to CoalesceStream
47298
47299
47300            trackDecodeInfo.clearDtsInfo(track);
47301            gops = alignedGops;
47302          }
47303
47304          trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
47305          // samples (that is, frames) in the video data
47306
47307          track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
47308
47309          mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
47310          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
47311          this.trigger('processedGopsInfo', gops.map(function (gop) {
47312            return {
47313              pts: gop.pts,
47314              dts: gop.dts,
47315              byteLength: gop.byteLength
47316            };
47317          }));
47318          firstGop = gops[0];
47319          lastGop = gops[gops.length - 1];
47320          this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
47321          this.trigger('timingInfo', {
47322            start: gops[0].pts,
47323            end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
47324          }); // save all the nals in the last GOP into the gop cache
47325
47326          this.gopCache_.unshift({
47327            gop: gops.pop(),
47328            pps: track.pps,
47329            sps: track.sps
47330          }); // Keep a maximum of 6 GOPs in the cache
47331
47332          this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
47333
47334          nalUnits = [];
47335          this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
47336          this.trigger('timelineStartInfo', track.timelineStartInfo);
47337          moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
47338          // throwing away hundreds of media segment fragments
47339
47340          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
47341
47342          sequenceNumber++;
47343          boxes.set(moof);
47344          boxes.set(mdat, moof.byteLength);
47345          this.trigger('data', {
47346            track: track,
47347            boxes: boxes
47348          });
47349          this.resetStream_(); // Continue with the flush process now
47350
47351          this.trigger('done', 'VideoSegmentStream');
47352        };
47353
47354        this.reset = function () {
47355          this.resetStream_();
47356          nalUnits = [];
47357          this.gopCache_.length = 0;
47358          gopsToAlignWith.length = 0;
47359          this.trigger('reset');
47360        };
47361
47362        this.resetStream_ = function () {
47363          trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
47364          // for instance, when we are rendition switching
47365
47366          config = undefined;
47367          pps = undefined;
47368        }; // Search for a candidate Gop for gop-fusion from the gop cache and
47369        // return it or return null if no good candidate was found
47370
47371
47372        this.getGopForFusion_ = function (nalUnit) {
47373          var halfSecond = 45000,
47374              // Half-a-second in a 90khz clock
47375          allowableOverlap = 10000,
47376              // About 3 frames @ 30fps
47377          nearestDistance = Infinity,
47378              dtsDistance,
47379              nearestGopObj,
47380              currentGop,
47381              currentGopObj,
47382              i; // Search for the GOP nearest to the beginning of this nal unit
47383
47384          for (i = 0; i < this.gopCache_.length; i++) {
47385            currentGopObj = this.gopCache_[i];
47386            currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
47387
47388            if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
47389              continue;
47390            } // Reject Gops that would require a negative baseMediaDecodeTime
47391
47392
47393            if (currentGop.dts < track.timelineStartInfo.dts) {
47394              continue;
47395            } // The distance between the end of the gop and the start of the nalUnit
47396
47397
47398            dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
47399            // a half-second of the nal unit
47400
47401            if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
47402              // Always use the closest GOP we found if there is more than
47403              // one candidate
47404              if (!nearestGopObj || nearestDistance > dtsDistance) {
47405                nearestGopObj = currentGopObj;
47406                nearestDistance = dtsDistance;
47407              }
47408            }
47409          }
47410
47411          if (nearestGopObj) {
47412            return nearestGopObj.gop;
47413          }
47414
47415          return null;
47416        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
47417        // of gopsToAlignWith starting from the START of the list
47418
47419
47420        this.alignGopsAtStart_ = function (gops) {
47421          var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
47422          byteLength = gops.byteLength;
47423          nalCount = gops.nalCount;
47424          duration = gops.duration;
47425          alignIndex = gopIndex = 0;
47426
47427          while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
47428            align = gopsToAlignWith[alignIndex];
47429            gop = gops[gopIndex];
47430
47431            if (align.pts === gop.pts) {
47432              break;
47433            }
47434
47435            if (gop.pts > align.pts) {
47436              // this current gop starts after the current gop we want to align on, so increment
47437              // align index
47438              alignIndex++;
47439              continue;
47440            } // current gop starts before the current gop we want to align on. so increment gop
47441            // index
47442
47443
47444            gopIndex++;
47445            byteLength -= gop.byteLength;
47446            nalCount -= gop.nalCount;
47447            duration -= gop.duration;
47448          }
47449
47450          if (gopIndex === 0) {
47451            // no gops to trim
47452            return gops;
47453          }
47454
47455          if (gopIndex === gops.length) {
47456            // all gops trimmed, skip appending all gops
47457            return null;
47458          }
47459
47460          alignedGops = gops.slice(gopIndex);
47461          alignedGops.byteLength = byteLength;
47462          alignedGops.duration = duration;
47463          alignedGops.nalCount = nalCount;
47464          alignedGops.pts = alignedGops[0].pts;
47465          alignedGops.dts = alignedGops[0].dts;
47466          return alignedGops;
47467        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
47468        // of gopsToAlignWith starting from the END of the list
47469
47470
47471        this.alignGopsAtEnd_ = function (gops) {
47472          var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
47473          alignIndex = gopsToAlignWith.length - 1;
47474          gopIndex = gops.length - 1;
47475          alignEndIndex = null;
47476          matchFound = false;
vendor: 4,848 bytes, lines 47477-47623
47477
47478          while (alignIndex >= 0 && gopIndex >= 0) {
47479            align = gopsToAlignWith[alignIndex];
47480            gop = gops[gopIndex];
47481
47482            if (align.pts === gop.pts) {
47483              matchFound = true;
47484              break;
47485            }
47486
47487            if (align.pts > gop.pts) {
47488              alignIndex--;
47489              continue;
47490            }
47491
47492            if (alignIndex === gopsToAlignWith.length - 1) {
47493              // gop.pts is greater than the last alignment candidate. If no match is found
47494              // by the end of this loop, we still want to append gops that come after this
47495              // point
47496              alignEndIndex = gopIndex;
47497            }
47498
47499            gopIndex--;
47500          }
47501
47502          if (!matchFound && alignEndIndex === null) {
47503            return null;
47504          }
47505
47506          var trimIndex;
47507
47508          if (matchFound) {
47509            trimIndex = gopIndex;
47510          } else {
47511            trimIndex = alignEndIndex;
47512          }
47513
47514          if (trimIndex === 0) {
47515            return gops;
47516          }
47517
47518          var alignedGops = gops.slice(trimIndex);
47519          var metadata = alignedGops.reduce(function (total, gop) {
47520            total.byteLength += gop.byteLength;
47521            total.duration += gop.duration;
47522            total.nalCount += gop.nalCount;
47523            return total;
47524          }, {
47525            byteLength: 0,
47526            duration: 0,
47527            nalCount: 0
47528          });
47529          alignedGops.byteLength = metadata.byteLength;
47530          alignedGops.duration = metadata.duration;
47531          alignedGops.nalCount = metadata.nalCount;
47532          alignedGops.pts = alignedGops[0].pts;
47533          alignedGops.dts = alignedGops[0].dts;
47534          return alignedGops;
47535        };
47536
47537        this.alignGopsWith = function (newGopsToAlignWith) {
47538          gopsToAlignWith = newGopsToAlignWith;
47539        };
47540      };
47541
47542      _VideoSegmentStream.prototype = new stream();
47543      /**
47544       * A Stream that can combine multiple streams (ie. audio & video)
47545       * into a single output segment for MSE. Also supports audio-only
47546       * and video-only streams.
47547       * @param options {object} transmuxer options object
47548       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
47549       *        in the source; false to adjust the first segment to start at media timeline start.
47550       */
47551
47552      _CoalesceStream = function CoalesceStream(options, metadataStream) {
47553        // Number of Tracks per output segment
47554        // If greater than 1, we combine multiple
47555        // tracks into a single segment
47556        this.numberOfTracks = 0;
47557        this.metadataStream = metadataStream;
47558        options = options || {};
47559
47560        if (typeof options.remux !== 'undefined') {
47561          this.remuxTracks = !!options.remux;
47562        } else {
47563          this.remuxTracks = true;
47564        }
47565
47566        if (typeof options.keepOriginalTimestamps === 'boolean') {
47567          this.keepOriginalTimestamps = options.keepOriginalTimestamps;
47568        } else {
47569          this.keepOriginalTimestamps = false;
47570        }
47571
47572        this.pendingTracks = [];
47573        this.videoTrack = null;
47574        this.pendingBoxes = [];
47575        this.pendingCaptions = [];
47576        this.pendingMetadata = [];
47577        this.pendingBytes = 0;
47578        this.emittedTracks = 0;
47579
47580        _CoalesceStream.prototype.init.call(this); // Take output from multiple
47581
47582
47583        this.push = function (output) {
47584          // buffer incoming captions until the associated video segment
47585          // finishes
47586          if (output.text) {
47587            return this.pendingCaptions.push(output);
47588          } // buffer incoming id3 tags until the final flush
47589
47590
47591          if (output.frames) {
47592            return this.pendingMetadata.push(output);
47593          } // Add this track to the list of pending tracks and store
47594          // important information required for the construction of
47595          // the final segment
47596
47597
47598          this.pendingTracks.push(output.track);
47599          this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
47600          // We unshift audio and push video because
47601          // as of Chrome 75 when switching from
47602          // one init segment to another if the video
47603          // mdat does not appear after the audio mdat
47604          // only audio will play for the duration of our transmux.
47605
47606          if (output.track.type === 'video') {
47607            this.videoTrack = output.track;
47608            this.pendingBoxes.push(output.boxes);
47609          }
47610
47611          if (output.track.type === 'audio') {
47612            this.audioTrack = output.track;
47613            this.pendingBoxes.unshift(output.boxes);
47614          }
47615        };
47616      };
47617
47618      _CoalesceStream.prototype = new stream();
47619
47620      _CoalesceStream.prototype.flush = function (flushSource) {
47621        var offset = 0,
47622            event = {
47623          captions: [],
vendor: 4,722 bytes, lines 47624-47729
47624          captionStreams: {},
47625          metadata: [],
47626          info: {}
47627        },
47628            caption,
47629            id3,
47630            initSegment,
47631            timelineStartPts = 0,
47632            i;
47633
47634        if (this.pendingTracks.length < this.numberOfTracks) {
47635          if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
47636            // Return because we haven't received a flush from a data-generating
47637            // portion of the segment (meaning that we have only recieved meta-data
47638            // or captions.)
47639            return;
47640          } else if (this.remuxTracks) {
47641            // Return until we have enough tracks from the pipeline to remux (if we
47642            // are remuxing audio and video into a single MP4)
47643            return;
47644          } else if (this.pendingTracks.length === 0) {
47645            // In the case where we receive a flush without any data having been
47646            // received we consider it an emitted track for the purposes of coalescing
47647            // `done` events.
47648            // We do this for the case where there is an audio and video track in the
47649            // segment but no audio data. (seen in several playlists with alternate
47650            // audio tracks and no audio present in the main TS segments.)
47651            this.emittedTracks++;
47652
47653            if (this.emittedTracks >= this.numberOfTracks) {
47654              this.trigger('done');
47655              this.emittedTracks = 0;
47656            }
47657
47658            return;
47659          }
47660        }
47661
47662        if (this.videoTrack) {
47663          timelineStartPts = this.videoTrack.timelineStartInfo.pts;
47664          videoProperties.forEach(function (prop) {
47665            event.info[prop] = this.videoTrack[prop];
47666          }, this);
47667        } else if (this.audioTrack) {
47668          timelineStartPts = this.audioTrack.timelineStartInfo.pts;
47669          audioProperties.forEach(function (prop) {
47670            event.info[prop] = this.audioTrack[prop];
47671          }, this);
47672        }
47673
47674        if (this.videoTrack || this.audioTrack) {
47675          if (this.pendingTracks.length === 1) {
47676            event.type = this.pendingTracks[0].type;
47677          } else {
47678            event.type = 'combined';
47679          }
47680
47681          this.emittedTracks += this.pendingTracks.length;
47682          initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
47683
47684          event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
47685          // and track definitions
47686
47687          event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
47688
47689          event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
47690
47691          for (i = 0; i < this.pendingBoxes.length; i++) {
47692            event.data.set(this.pendingBoxes[i], offset);
47693            offset += this.pendingBoxes[i].byteLength;
47694          } // Translate caption PTS times into second offsets to match the
47695          // video timeline for the segment, and add track info
47696
47697
47698          for (i = 0; i < this.pendingCaptions.length; i++) {
47699            caption = this.pendingCaptions[i];
47700            caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
47701            caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
47702            event.captionStreams[caption.stream] = true;
47703            event.captions.push(caption);
47704          } // Translate ID3 frame PTS times into second offsets to match the
47705          // video timeline for the segment
47706
47707
47708          for (i = 0; i < this.pendingMetadata.length; i++) {
47709            id3 = this.pendingMetadata[i];
47710            id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
47711            event.metadata.push(id3);
47712          } // We add this to every single emitted segment even though we only need
47713          // it for the first
47714
47715
47716          event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
47717
47718          this.pendingTracks.length = 0;
47719          this.videoTrack = null;
47720          this.pendingBoxes.length = 0;
47721          this.pendingCaptions.length = 0;
47722          this.pendingBytes = 0;
47723          this.pendingMetadata.length = 0; // Emit the built segment
47724          // We include captions and ID3 tags for backwards compatibility,
47725          // ideally we should send only video and audio in the data event
47726
47727          this.trigger('data', event); // Emit each caption to the outside world
47728          // Ideally, this would happen immediately on parsing captions,
47729          // but we need to ensure that video data is sent back first
vendor: 11,951 bytes, lines 47730-47987
47730          // so that caption timing can be adjusted to match video timing
47731
47732          for (i = 0; i < event.captions.length; i++) {
47733            caption = event.captions[i];
47734            this.trigger('caption', caption);
47735          } // Emit each id3 tag to the outside world
47736          // Ideally, this would happen immediately on parsing the tag,
47737          // but we need to ensure that video data is sent back first
47738          // so that ID3 frame timing can be adjusted to match video timing
47739
47740
47741          for (i = 0; i < event.metadata.length; i++) {
47742            id3 = event.metadata[i];
47743            this.trigger('id3Frame', id3);
47744          }
47745        } // Only emit `done` if all tracks have been flushed and emitted
47746
47747
47748        if (this.emittedTracks >= this.numberOfTracks) {
47749          this.trigger('done');
47750          this.emittedTracks = 0;
47751        }
47752      };
47753
47754      _CoalesceStream.prototype.setRemux = function (val) {
47755        this.remuxTracks = val;
47756      };
47757      /**
47758       * A Stream that expects MP2T binary data as input and produces
47759       * corresponding media segments, suitable for use with Media Source
47760       * Extension (MSE) implementations that support the ISO BMFF byte
47761       * stream format, like Chrome.
47762       */
47763
47764
47765      _Transmuxer = function Transmuxer(options) {
47766        var self = this,
47767            hasFlushed = true,
47768            videoTrack,
47769            audioTrack;
47770
47771        _Transmuxer.prototype.init.call(this);
47772
47773        options = options || {};
47774        this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
47775        this.transmuxPipeline_ = {};
47776
47777        this.setupAacPipeline = function () {
47778          var pipeline = {};
47779          this.transmuxPipeline_ = pipeline;
47780          pipeline.type = 'aac';
47781          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
47782
47783          pipeline.aacStream = new aac();
47784          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
47785          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
47786          pipeline.adtsStream = new adts();
47787          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
47788          pipeline.headOfPipeline = pipeline.aacStream;
47789          pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
47790          pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
47791          pipeline.metadataStream.on('timestamp', function (frame) {
47792            pipeline.aacStream.setTimestamp(frame.timeStamp);
47793          });
47794          pipeline.aacStream.on('data', function (data) {
47795            if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
47796              return;
47797            }
47798
47799            audioTrack = audioTrack || {
47800              timelineStartInfo: {
47801                baseMediaDecodeTime: self.baseMediaDecodeTime
47802              },
47803              codec: 'adts',
47804              type: 'audio'
47805            }; // hook up the audio segment stream to the first track with aac data
47806
47807            pipeline.coalesceStream.numberOfTracks++;
47808            pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
47809            pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
47810
47811            pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
47812
47813            self.trigger('trackinfo', {
47814              hasAudio: !!audioTrack,
47815              hasVideo: !!videoTrack
47816            });
47817          }); // Re-emit any data coming from the coalesce stream to the outside world
47818
47819          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
47820
47821          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
47822        };
47823
47824        this.setupTsPipeline = function () {
47825          var pipeline = {};
47826          this.transmuxPipeline_ = pipeline;
47827          pipeline.type = 'ts';
47828          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
47829
47830          pipeline.packetStream = new m2ts_1.TransportPacketStream();
47831          pipeline.parseStream = new m2ts_1.TransportParseStream();
47832          pipeline.elementaryStream = new m2ts_1.ElementaryStream();
47833          pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream();
47834          pipeline.adtsStream = new adts();
47835          pipeline.h264Stream = new H264Stream$1();
47836          pipeline.captionStream = new m2ts_1.CaptionStream();
47837          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
47838          pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
47839
47840          pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
47841          // demux the streams
47842
47843          pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
47844          pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
47845          pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
47846
47847          pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
47848          pipeline.elementaryStream.on('data', function (data) {
47849            var i;
47850
47851            if (data.type === 'metadata') {
47852              i = data.tracks.length; // scan the tracks listed in the metadata
47853
47854              while (i--) {
47855                if (!videoTrack && data.tracks[i].type === 'video') {
47856                  videoTrack = data.tracks[i];
47857                  videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
47858                } else if (!audioTrack && data.tracks[i].type === 'audio') {
47859                  audioTrack = data.tracks[i];
47860                  audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
47861                }
47862              } // hook up the video segment stream to the first track with h264 data
47863
47864
47865              if (videoTrack && !pipeline.videoSegmentStream) {
47866                pipeline.coalesceStream.numberOfTracks++;
47867                pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
47868                pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
47869                  // When video emits timelineStartInfo data after a flush, we forward that
47870                  // info to the AudioSegmentStream, if it exists, because video timeline
47871                  // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
47872                  // because this is a particularly subtle form of timestamp alteration.
47873                  if (audioTrack && !options.keepOriginalTimestamps) {
47874                    audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
47875                    // very earliest DTS we have seen in video because Chrome will
47876                    // interpret any video track with a baseMediaDecodeTime that is
47877                    // non-zero as a gap.
47878
47879                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
47880                  }
47881                });
47882                pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
47883                pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
47884                pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
47885                  if (audioTrack) {
47886                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
47887                  }
47888                });
47889                pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
47890
47891                pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
47892              }
47893
47894              if (audioTrack && !pipeline.audioSegmentStream) {
47895                // hook up the audio segment stream to the first track with aac data
47896                pipeline.coalesceStream.numberOfTracks++;
47897                pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
47898                pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
47899
47900                pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
47901              } // emit pmt info
47902
47903
47904              self.trigger('trackinfo', {
47905                hasAudio: !!audioTrack,
47906                hasVideo: !!videoTrack
47907              });
47908            }
47909          }); // Re-emit any data coming from the coalesce stream to the outside world
47910
47911          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
47912          pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
47913            id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
47914            self.trigger('id3Frame', id3Frame);
47915          });
47916          pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
47917
47918          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
47919        }; // hook up the segment streams once track metadata is delivered
47920
47921
47922        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
47923          var pipeline = this.transmuxPipeline_;
47924
47925          if (!options.keepOriginalTimestamps) {
47926            this.baseMediaDecodeTime = baseMediaDecodeTime;
47927          }
47928
47929          if (audioTrack) {
47930            audioTrack.timelineStartInfo.dts = undefined;
47931            audioTrack.timelineStartInfo.pts = undefined;
47932            trackDecodeInfo.clearDtsInfo(audioTrack);
47933
47934            if (pipeline.audioTimestampRolloverStream) {
47935              pipeline.audioTimestampRolloverStream.discontinuity();
47936            }
47937          }
47938
47939          if (videoTrack) {
47940            if (pipeline.videoSegmentStream) {
47941              pipeline.videoSegmentStream.gopCache_ = [];
47942            }
47943
47944            videoTrack.timelineStartInfo.dts = undefined;
47945            videoTrack.timelineStartInfo.pts = undefined;
47946            trackDecodeInfo.clearDtsInfo(videoTrack);
47947            pipeline.captionStream.reset();
47948          }
47949
47950          if (pipeline.timestampRolloverStream) {
47951            pipeline.timestampRolloverStream.discontinuity();
47952          }
47953        };
47954
47955        this.setAudioAppendStart = function (timestamp) {
47956          if (audioTrack) {
47957            this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
47958          }
47959        };
47960
47961        this.setRemux = function (val) {
47962          var pipeline = this.transmuxPipeline_;
47963          options.remux = val;
47964
47965          if (pipeline && pipeline.coalesceStream) {
47966            pipeline.coalesceStream.setRemux(val);
47967          }
47968        };
47969
47970        this.alignGopsWith = function (gopsToAlignWith) {
47971          if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
47972            this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
47973          }
47974        }; // feed incoming data to the front of the parsing pipeline
47975
47976
47977        this.push = function (data) {
47978          if (hasFlushed) {
47979            var isAac = isLikelyAacData$1(data);
47980
47981            if (isAac && this.transmuxPipeline_.type !== 'aac') {
47982              this.setupAacPipeline();
47983            } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
47984              this.setupTsPipeline();
47985            }
47986
47987            hasFlushed = false;
vendor: 33,297 bytes, lines 47988-48942
47988          }
47989
47990          this.transmuxPipeline_.headOfPipeline.push(data);
47991        }; // flush any buffered data
47992
47993
47994        this.flush = function () {
47995          hasFlushed = true; // Start at the top of the pipeline and flush all pending work
47996
47997          this.transmuxPipeline_.headOfPipeline.flush();
47998        };
47999
48000        this.endTimeline = function () {
48001          this.transmuxPipeline_.headOfPipeline.endTimeline();
48002        };
48003
48004        this.reset = function () {
48005          if (this.transmuxPipeline_.headOfPipeline) {
48006            this.transmuxPipeline_.headOfPipeline.reset();
48007          }
48008        }; // Caption data has to be reset when seeking outside buffered range
48009
48010
48011        this.resetCaptions = function () {
48012          if (this.transmuxPipeline_.captionStream) {
48013            this.transmuxPipeline_.captionStream.reset();
48014          }
48015        };
48016      };
48017
48018      _Transmuxer.prototype = new stream();
48019      var transmuxer = {
48020        Transmuxer: _Transmuxer,
48021        VideoSegmentStream: _VideoSegmentStream,
48022        AudioSegmentStream: _AudioSegmentStream,
48023        AUDIO_PROPERTIES: audioProperties,
48024        VIDEO_PROPERTIES: videoProperties,
48025        // exported for testing
48026        generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
48027      };
48028      var transmuxer_1 = transmuxer.Transmuxer;
48029      /**
48030       * mux.js
48031       *
48032       * Copyright (c) Brightcove
48033       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48034       */
48035
48036      var codecs = {
48037        Adts: adts,
48038        h264: h264
48039      };
48040      var ONE_SECOND_IN_TS$4 = clock.ONE_SECOND_IN_TS;
48041      /**
48042       * Constructs a single-track, ISO BMFF media segment from AAC data
48043       * events. The output of this stream can be fed to a SourceBuffer
48044       * configured with a suitable initialization segment.
48045       */
48046
48047      var AudioSegmentStream$1 = function AudioSegmentStream$1(track, options) {
48048        var adtsFrames = [],
48049            sequenceNumber = 0,
48050            earliestAllowedDts = 0,
48051            audioAppendStartTs = 0,
48052            videoBaseMediaDecodeTime = Infinity,
48053            segmentStartPts = null,
48054            segmentEndPts = null;
48055        options = options || {};
48056        AudioSegmentStream$1.prototype.init.call(this);
48057
48058        this.push = function (data) {
48059          trackDecodeInfo.collectDtsInfo(track, data);
48060
48061          if (track) {
48062            audioProperties.forEach(function (prop) {
48063              track[prop] = data[prop];
48064            });
48065          } // buffer audio data until end() is called
48066
48067
48068          adtsFrames.push(data);
48069        };
48070
48071        this.setEarliestDts = function (earliestDts) {
48072          earliestAllowedDts = earliestDts;
48073        };
48074
48075        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
48076          videoBaseMediaDecodeTime = baseMediaDecodeTime;
48077        };
48078
48079        this.setAudioAppendStart = function (timestamp) {
48080          audioAppendStartTs = timestamp;
48081        };
48082
48083        this.processFrames_ = function () {
48084          var frames, moof, mdat, boxes, timingInfo; // return early if no audio data has been observed
48085
48086          if (adtsFrames.length === 0) {
48087            return;
48088          }
48089
48090          frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
48091
48092          if (frames.length === 0) {
48093            // return early if the frames are all after the earliest allowed DTS
48094            // TODO should we clear the adtsFrames?
48095            return;
48096          }
48097
48098          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
48099          audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
48100          // samples (that is, adts frames) in the audio data
48101
48102          track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
48103
48104          mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
48105          adtsFrames = [];
48106          moof = mp4Generator.moof(sequenceNumber, [track]); // bump the sequence number for next time
48107
48108          sequenceNumber++;
48109          track.initSegment = mp4Generator.initSegment([track]); // it would be great to allocate this array up front instead of
48110          // throwing away hundreds of media segment fragments
48111
48112          boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
48113          boxes.set(moof);
48114          boxes.set(mdat, moof.byteLength);
48115          trackDecodeInfo.clearDtsInfo(track);
48116
48117          if (segmentStartPts === null) {
48118            segmentEndPts = segmentStartPts = frames[0].pts;
48119          }
48120
48121          segmentEndPts += frames.length * (ONE_SECOND_IN_TS$4 * 1024 / track.samplerate);
48122          timingInfo = {
48123            start: segmentStartPts
48124          };
48125          this.trigger('timingInfo', timingInfo);
48126          this.trigger('data', {
48127            track: track,
48128            boxes: boxes
48129          });
48130        };
48131
48132        this.flush = function () {
48133          this.processFrames_(); // trigger final timing info
48134
48135          this.trigger('timingInfo', {
48136            start: segmentStartPts,
48137            end: segmentEndPts
48138          });
48139          this.resetTiming_();
48140          this.trigger('done', 'AudioSegmentStream');
48141        };
48142
48143        this.partialFlush = function () {
48144          this.processFrames_();
48145          this.trigger('partialdone', 'AudioSegmentStream');
48146        };
48147
48148        this.endTimeline = function () {
48149          this.flush();
48150          this.trigger('endedtimeline', 'AudioSegmentStream');
48151        };
48152
48153        this.resetTiming_ = function () {
48154          trackDecodeInfo.clearDtsInfo(track);
48155          segmentStartPts = null;
48156          segmentEndPts = null;
48157        };
48158
48159        this.reset = function () {
48160          this.resetTiming_();
48161          adtsFrames = [];
48162          this.trigger('reset');
48163        };
48164      };
48165
48166      AudioSegmentStream$1.prototype = new stream();
48167      var audioSegmentStream = AudioSegmentStream$1;
48168
48169      var VideoSegmentStream$1 = function VideoSegmentStream$1(track, options) {
48170        var sequenceNumber = 0,
48171            nalUnits = [],
48172            frameCache = [],
48173            // gopsToAlignWith = [],
48174        config,
48175            pps,
48176            segmentStartPts = null,
48177            segmentEndPts = null,
48178            gops,
48179            ensureNextFrameIsKeyFrame = true;
48180        options = options || {};
48181        VideoSegmentStream$1.prototype.init.call(this);
48182
48183        this.push = function (nalUnit) {
48184          trackDecodeInfo.collectDtsInfo(track, nalUnit);
48185
48186          if (typeof track.timelineStartInfo.dts === 'undefined') {
48187            track.timelineStartInfo.dts = nalUnit.dts;
48188          } // record the track config
48189
48190
48191          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
48192            config = nalUnit.config;
48193            track.sps = [nalUnit.data];
48194            videoProperties.forEach(function (prop) {
48195              track[prop] = config[prop];
48196            }, this);
48197          }
48198
48199          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
48200            pps = nalUnit.data;
48201            track.pps = [nalUnit.data];
48202          } // buffer video until flush() is called
48203
48204
48205          nalUnits.push(nalUnit);
48206        };
48207
48208        this.processNals_ = function (cacheLastFrame) {
48209          var i;
48210          nalUnits = frameCache.concat(nalUnits); // Throw away nalUnits at the start of the byte stream until
48211          // we find the first AUD
48212
48213          while (nalUnits.length) {
48214            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
48215              break;
48216            }
48217
48218            nalUnits.shift();
48219          } // Return early if no video data has been observed
48220
48221
48222          if (nalUnits.length === 0) {
48223            return;
48224          }
48225
48226          var frames = frameUtils.groupNalsIntoFrames(nalUnits);
48227
48228          if (!frames.length) {
48229            return;
48230          } // note that the frame cache may also protect us from cases where we haven't
48231          // pushed data for the entire first or last frame yet
48232
48233
48234          frameCache = frames[frames.length - 1];
48235
48236          if (cacheLastFrame) {
48237            frames.pop();
48238            frames.duration -= frameCache.duration;
48239            frames.nalCount -= frameCache.length;
48240            frames.byteLength -= frameCache.byteLength;
48241          }
48242
48243          if (!frames.length) {
48244            nalUnits = [];
48245            return;
48246          }
48247
48248          this.trigger('timelineStartInfo', track.timelineStartInfo);
48249
48250          if (ensureNextFrameIsKeyFrame) {
48251            gops = frameUtils.groupFramesIntoGops(frames);
48252
48253            if (!gops[0][0].keyFrame) {
48254              gops = frameUtils.extendFirstKeyFrame(gops);
48255
48256              if (!gops[0][0].keyFrame) {
48257                // we haven't yet gotten a key frame, so reset nal units to wait for more nal
48258                // units
48259                nalUnits = [].concat.apply([], frames).concat(frameCache);
48260                frameCache = [];
48261                return;
48262              }
48263
48264              frames = [].concat.apply([], gops);
48265              frames.duration = gops.duration;
48266            }
48267
48268            ensureNextFrameIsKeyFrame = false;
48269          }
48270
48271          if (segmentStartPts === null) {
48272            segmentStartPts = frames[0].pts;
48273            segmentEndPts = segmentStartPts;
48274          }
48275
48276          segmentEndPts += frames.duration;
48277          this.trigger('timingInfo', {
48278            start: segmentStartPts,
48279            end: segmentEndPts
48280          });
48281
48282          for (i = 0; i < frames.length; i++) {
48283            var frame = frames[i];
48284            track.samples = frameUtils.generateSampleTableForFrame(frame);
48285            var mdat = mp4Generator.mdat(frameUtils.concatenateNalDataForFrame(frame));
48286            trackDecodeInfo.clearDtsInfo(track);
48287            trackDecodeInfo.collectDtsInfo(track, frame);
48288            track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
48289            var moof = mp4Generator.moof(sequenceNumber, [track]);
48290            sequenceNumber++;
48291            track.initSegment = mp4Generator.initSegment([track]);
48292            var boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
48293            boxes.set(moof);
48294            boxes.set(mdat, moof.byteLength);
48295            this.trigger('data', {
48296              track: track,
48297              boxes: boxes,
48298              sequence: sequenceNumber,
48299              videoFrameDts: frame.dts,
48300              videoFramePts: frame.pts
48301            });
48302          }
48303
48304          nalUnits = [];
48305        };
48306
48307        this.resetTimingAndConfig_ = function () {
48308          config = undefined;
48309          pps = undefined;
48310          segmentStartPts = null;
48311          segmentEndPts = null;
48312        };
48313
48314        this.partialFlush = function () {
48315          this.processNals_(true);
48316          this.trigger('partialdone', 'VideoSegmentStream');
48317        };
48318
48319        this.flush = function () {
48320          this.processNals_(false); // reset config and pps because they may differ across segments
48321          // for instance, when we are rendition switching
48322
48323          this.resetTimingAndConfig_();
48324          this.trigger('done', 'VideoSegmentStream');
48325        };
48326
48327        this.endTimeline = function () {
48328          this.flush();
48329          this.trigger('endedtimeline', 'VideoSegmentStream');
48330        };
48331
48332        this.reset = function () {
48333          this.resetTimingAndConfig_();
48334          frameCache = [];
48335          nalUnits = [];
48336          ensureNextFrameIsKeyFrame = true;
48337          this.trigger('reset');
48338        };
48339      };
48340
48341      VideoSegmentStream$1.prototype = new stream();
48342      var videoSegmentStream = VideoSegmentStream$1;
48343      var isLikelyAacData$2 = utils.isLikelyAacData;
48344
48345      var createPipeline = function createPipeline(object) {
48346        object.prototype = new stream();
48347        object.prototype.init.call(object);
48348        return object;
48349      };
48350
48351      var tsPipeline = function tsPipeline(options) {
48352        var pipeline = {
48353          type: 'ts',
48354          tracks: {
48355            audio: null,
48356            video: null
48357          },
48358          packet: new m2ts_1.TransportPacketStream(),
48359          parse: new m2ts_1.TransportParseStream(),
48360          elementary: new m2ts_1.ElementaryStream(),
48361          timestampRollover: new m2ts_1.TimestampRolloverStream(),
48362          adts: new codecs.Adts(),
48363          h264: new codecs.h264.H264Stream(),
48364          captionStream: new m2ts_1.CaptionStream(),
48365          metadataStream: new m2ts_1.MetadataStream()
48366        };
48367        pipeline.headOfPipeline = pipeline.packet; // Transport Stream
48368
48369        pipeline.packet.pipe(pipeline.parse).pipe(pipeline.elementary).pipe(pipeline.timestampRollover); // H264
48370
48371        pipeline.timestampRollover.pipe(pipeline.h264); // Hook up CEA-608/708 caption stream
48372
48373        pipeline.h264.pipe(pipeline.captionStream);
48374        pipeline.timestampRollover.pipe(pipeline.metadataStream); // ADTS
48375
48376        pipeline.timestampRollover.pipe(pipeline.adts);
48377        pipeline.elementary.on('data', function (data) {
48378          if (data.type !== 'metadata') {
48379            return;
48380          }
48381
48382          for (var i = 0; i < data.tracks.length; i++) {
48383            if (!pipeline.tracks[data.tracks[i].type]) {
48384              pipeline.tracks[data.tracks[i].type] = data.tracks[i];
48385              pipeline.tracks[data.tracks[i].type].timelineStartInfo.baseMediaDecodeTime = options.baseMediaDecodeTime;
48386            }
48387          }
48388
48389          if (pipeline.tracks.video && !pipeline.videoSegmentStream) {
48390            pipeline.videoSegmentStream = new videoSegmentStream(pipeline.tracks.video, options);
48391            pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
48392              if (pipeline.tracks.audio && !options.keepOriginalTimestamps) {
48393                pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - options.baseMediaDecodeTime);
48394              }
48395            });
48396            pipeline.videoSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'videoTimingInfo'));
48397            pipeline.videoSegmentStream.on('data', function (data) {
48398              pipeline.trigger('data', {
48399                type: 'video',
48400                data: data
48401              });
48402            });
48403            pipeline.videoSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done'));
48404            pipeline.videoSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone'));
48405            pipeline.videoSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline'));
48406            pipeline.h264.pipe(pipeline.videoSegmentStream);
48407          }
48408
48409          if (pipeline.tracks.audio && !pipeline.audioSegmentStream) {
48410            pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options);
48411            pipeline.audioSegmentStream.on('data', function (data) {
48412              pipeline.trigger('data', {
48413                type: 'audio',
48414                data: data
48415              });
48416            });
48417            pipeline.audioSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done'));
48418            pipeline.audioSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone'));
48419            pipeline.audioSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline'));
48420            pipeline.audioSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'audioTimingInfo'));
48421            pipeline.adts.pipe(pipeline.audioSegmentStream);
48422          } // emit pmt info
48423
48424
48425          pipeline.trigger('trackinfo', {
48426            hasAudio: !!pipeline.tracks.audio,
48427            hasVideo: !!pipeline.tracks.video
48428          });
48429        });
48430        pipeline.captionStream.on('data', function (caption) {
48431          var timelineStartPts;
48432
48433          if (pipeline.tracks.video) {
48434            timelineStartPts = pipeline.tracks.video.timelineStartInfo.pts || 0;
48435          } else {
48436            // This will only happen if we encounter caption packets before
48437            // video data in a segment. This is an unusual/unlikely scenario,
48438            // so we assume the timeline starts at zero for now.
48439            timelineStartPts = 0;
48440          } // Translate caption PTS times into second offsets into the
48441          // video timeline for the segment
48442
48443
48444          caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, options.keepOriginalTimestamps);
48445          caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, options.keepOriginalTimestamps);
48446          pipeline.trigger('caption', caption);
48447        });
48448        pipeline = createPipeline(pipeline);
48449        pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame'));
48450        return pipeline;
48451      };
48452
48453      var aacPipeline = function aacPipeline(options) {
48454        var pipeline = {
48455          type: 'aac',
48456          tracks: {
48457            audio: null
48458          },
48459          metadataStream: new m2ts_1.MetadataStream(),
48460          aacStream: new aac(),
48461          audioRollover: new m2ts_1.TimestampRolloverStream('audio'),
48462          timedMetadataRollover: new m2ts_1.TimestampRolloverStream('timed-metadata'),
48463          adtsStream: new adts(true)
48464        }; // set up the parsing pipeline
48465
48466        pipeline.headOfPipeline = pipeline.aacStream;
48467        pipeline.aacStream.pipe(pipeline.audioRollover).pipe(pipeline.adtsStream);
48468        pipeline.aacStream.pipe(pipeline.timedMetadataRollover).pipe(pipeline.metadataStream);
48469        pipeline.metadataStream.on('timestamp', function (frame) {
48470          pipeline.aacStream.setTimestamp(frame.timeStamp);
48471        });
48472        pipeline.aacStream.on('data', function (data) {
48473          if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
48474            return;
48475          }
48476
48477          pipeline.tracks.audio = pipeline.tracks.audio || {
48478            timelineStartInfo: {
48479              baseMediaDecodeTime: options.baseMediaDecodeTime
48480            },
48481            codec: 'adts',
48482            type: 'audio'
48483          }; // hook up the audio segment stream to the first track with aac data
48484
48485          pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options);
48486          pipeline.audioSegmentStream.on('data', function (data) {
48487            pipeline.trigger('data', {
48488              type: 'audio',
48489              data: data
48490            });
48491          });
48492          pipeline.audioSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone'));
48493          pipeline.audioSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done'));
48494          pipeline.audioSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline'));
48495          pipeline.audioSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'audioTimingInfo')); // Set up the final part of the audio pipeline
48496
48497          pipeline.adtsStream.pipe(pipeline.audioSegmentStream);
48498          pipeline.trigger('trackinfo', {
48499            hasAudio: !!pipeline.tracks.audio,
48500            hasVideo: !!pipeline.tracks.video
48501          });
48502        }); // set the pipeline up as a stream before binding to get access to the trigger function
48503
48504        pipeline = createPipeline(pipeline);
48505        pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame'));
48506        return pipeline;
48507      };
48508
48509      var setupPipelineListeners = function setupPipelineListeners(pipeline, transmuxer) {
48510        pipeline.on('data', transmuxer.trigger.bind(transmuxer, 'data'));
48511        pipeline.on('done', transmuxer.trigger.bind(transmuxer, 'done'));
48512        pipeline.on('partialdone', transmuxer.trigger.bind(transmuxer, 'partialdone'));
48513        pipeline.on('endedtimeline', transmuxer.trigger.bind(transmuxer, 'endedtimeline'));
48514        pipeline.on('audioTimingInfo', transmuxer.trigger.bind(transmuxer, 'audioTimingInfo'));
48515        pipeline.on('videoTimingInfo', transmuxer.trigger.bind(transmuxer, 'videoTimingInfo'));
48516        pipeline.on('trackinfo', transmuxer.trigger.bind(transmuxer, 'trackinfo'));
48517        pipeline.on('id3Frame', function (event) {
48518          // add this to every single emitted segment even though it's only needed for the first
48519          event.dispatchType = pipeline.metadataStream.dispatchType; // keep original time, can be adjusted if needed at a higher level
48520
48521          event.cueTime = clock.videoTsToSeconds(event.pts);
48522          transmuxer.trigger('id3Frame', event);
48523        });
48524        pipeline.on('caption', function (event) {
48525          transmuxer.trigger('caption', event);
48526        });
48527      };
48528
48529      var Transmuxer$1 = function Transmuxer$1(options) {
48530        var pipeline = null,
48531            hasFlushed = true;
48532        options = options || {};
48533        Transmuxer$1.prototype.init.call(this);
48534        options.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
48535
48536        this.push = function (bytes) {
48537          if (hasFlushed) {
48538            var isAac = isLikelyAacData$2(bytes);
48539
48540            if (isAac && (!pipeline || pipeline.type !== 'aac')) {
48541              pipeline = aacPipeline(options);
48542              setupPipelineListeners(pipeline, this);
48543            } else if (!isAac && (!pipeline || pipeline.type !== 'ts')) {
48544              pipeline = tsPipeline(options);
48545              setupPipelineListeners(pipeline, this);
48546            }
48547
48548            hasFlushed = false;
48549          }
48550
48551          pipeline.headOfPipeline.push(bytes);
48552        };
48553
48554        this.flush = function () {
48555          if (!pipeline) {
48556            return;
48557          }
48558
48559          hasFlushed = true;
48560          pipeline.headOfPipeline.flush();
48561        };
48562
48563        this.partialFlush = function () {
48564          if (!pipeline) {
48565            return;
48566          }
48567
48568          pipeline.headOfPipeline.partialFlush();
48569        };
48570
48571        this.endTimeline = function () {
48572          if (!pipeline) {
48573            return;
48574          }
48575
48576          pipeline.headOfPipeline.endTimeline();
48577        };
48578
48579        this.reset = function () {
48580          if (!pipeline) {
48581            return;
48582          }
48583
48584          pipeline.headOfPipeline.reset();
48585        };
48586
48587        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
48588          if (!options.keepOriginalTimestamps) {
48589            options.baseMediaDecodeTime = baseMediaDecodeTime;
48590          }
48591
48592          if (!pipeline) {
48593            return;
48594          }
48595
48596          if (pipeline.tracks.audio) {
48597            pipeline.tracks.audio.timelineStartInfo.dts = undefined;
48598            pipeline.tracks.audio.timelineStartInfo.pts = undefined;
48599            trackDecodeInfo.clearDtsInfo(pipeline.tracks.audio);
48600
48601            if (pipeline.audioRollover) {
48602              pipeline.audioRollover.discontinuity();
48603            }
48604          }
48605
48606          if (pipeline.tracks.video) {
48607            if (pipeline.videoSegmentStream) {
48608              pipeline.videoSegmentStream.gopCache_ = [];
48609            }
48610
48611            pipeline.tracks.video.timelineStartInfo.dts = undefined;
48612            pipeline.tracks.video.timelineStartInfo.pts = undefined;
48613            trackDecodeInfo.clearDtsInfo(pipeline.tracks.video); // pipeline.captionStream.reset();
48614          }
48615
48616          if (pipeline.timestampRollover) {
48617            pipeline.timestampRollover.discontinuity();
48618          }
48619        };
48620
48621        this.setRemux = function (val) {
48622          options.remux = val;
48623
48624          if (pipeline && pipeline.coalesceStream) {
48625            pipeline.coalesceStream.setRemux(val);
48626          }
48627        };
48628
48629        this.setAudioAppendStart = function (audioAppendStart) {
48630          if (!pipeline || !pipeline.tracks.audio || !pipeline.audioSegmentStream) {
48631            return;
48632          }
48633
48634          pipeline.audioSegmentStream.setAudioAppendStart(audioAppendStart);
48635        }; // TODO GOP alignment support
48636        // Support may be a bit trickier than with full segment appends, as GOPs may be split
48637        // and processed in a more granular fashion
48638
48639
48640        this.alignGopsWith = function (gopsToAlignWith) {
48641          return;
48642        };
48643      };
48644
48645      Transmuxer$1.prototype = new stream();
48646      var transmuxer$1 = Transmuxer$1;
48647      /**
48648       * mux.js
48649       *
48650       * Copyright (c) Brightcove
48651       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48652       */
48653
48654      var toUnsigned = function toUnsigned(value) {
48655        return value >>> 0;
48656      };
48657
48658      var toHexString = function toHexString(value) {
48659        return ('00' + value.toString(16)).slice(-2);
48660      };
48661
48662      var bin = {
48663        toUnsigned: toUnsigned,
48664        toHexString: toHexString
48665      };
48666
48667      var parseType$1 = function parseType$1(buffer) {
48668        var result = '';
48669        result += String.fromCharCode(buffer[0]);
48670        result += String.fromCharCode(buffer[1]);
48671        result += String.fromCharCode(buffer[2]);
48672        result += String.fromCharCode(buffer[3]);
48673        return result;
48674      };
48675
48676      var parseType_1 = parseType$1;
48677      var toUnsigned$1 = bin.toUnsigned;
48678
48679      var findBox = function findBox(data, path) {
48680        var results = [],
48681            i,
48682            size,
48683            type,
48684            end,
48685            subresults;
48686
48687        if (!path.length) {
48688          // short-circuit the search for empty paths
48689          return null;
48690        }
48691
48692        for (i = 0; i < data.byteLength;) {
48693          size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
48694          type = parseType_1(data.subarray(i + 4, i + 8));
48695          end = size > 1 ? i + size : data.byteLength;
48696
48697          if (type === path[0]) {
48698            if (path.length === 1) {
48699              // this is the end of the path and we've found the box we were
48700              // looking for
48701              results.push(data.subarray(i + 8, end));
48702            } else {
48703              // recursively search for the next box along the path
48704              subresults = findBox(data.subarray(i + 8, end), path.slice(1));
48705
48706              if (subresults.length) {
48707                results = results.concat(subresults);
48708              }
48709            }
48710          }
48711
48712          i = end;
48713        } // we've finished searching all of data
48714
48715
48716        return results;
48717      };
48718
48719      var findBox_1 = findBox;
48720      var toUnsigned$2 = bin.toUnsigned;
48721
48722      var tfdt = function tfdt(data) {
48723        var result = {
48724          version: data[0],
48725          flags: new Uint8Array(data.subarray(1, 4)),
48726          baseMediaDecodeTime: toUnsigned$2(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
48727        };
48728
48729        if (result.version === 1) {
48730          result.baseMediaDecodeTime *= Math.pow(2, 32);
48731          result.baseMediaDecodeTime += toUnsigned$2(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
48732        }
48733
48734        return result;
48735      };
48736
48737      var parseTfdt = tfdt;
48738
48739      var parseSampleFlags = function parseSampleFlags(flags) {
48740        return {
48741          isLeading: (flags[0] & 0x0c) >>> 2,
48742          dependsOn: flags[0] & 0x03,
48743          isDependedOn: (flags[1] & 0xc0) >>> 6,
48744          hasRedundancy: (flags[1] & 0x30) >>> 4,
48745          paddingValue: (flags[1] & 0x0e) >>> 1,
48746          isNonSyncSample: flags[1] & 0x01,
48747          degradationPriority: flags[2] << 8 | flags[3]
48748        };
48749      };
48750
48751      var parseSampleFlags_1 = parseSampleFlags;
48752
48753      var trun$1 = function trun$1(data) {
48754        var result = {
48755          version: data[0],
48756          flags: new Uint8Array(data.subarray(1, 4)),
48757          samples: []
48758        },
48759            view = new DataView(data.buffer, data.byteOffset, data.byteLength),
48760            // Flag interpretation
48761        dataOffsetPresent = result.flags[2] & 0x01,
48762            // compare with 2nd byte of 0x1
48763        firstSampleFlagsPresent = result.flags[2] & 0x04,
48764            // compare with 2nd byte of 0x4
48765        sampleDurationPresent = result.flags[1] & 0x01,
48766            // compare with 2nd byte of 0x100
48767        sampleSizePresent = result.flags[1] & 0x02,
48768            // compare with 2nd byte of 0x200
48769        sampleFlagsPresent = result.flags[1] & 0x04,
48770            // compare with 2nd byte of 0x400
48771        sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
48772            // compare with 2nd byte of 0x800
48773        sampleCount = view.getUint32(4),
48774            offset = 8,
48775            sample;
48776
48777        if (dataOffsetPresent) {
48778          // 32 bit signed integer
48779          result.dataOffset = view.getInt32(offset);
48780          offset += 4;
48781        } // Overrides the flags for the first sample only. The order of
48782        // optional values will be: duration, size, compositionTimeOffset
48783
48784
48785        if (firstSampleFlagsPresent && sampleCount) {
48786          sample = {
48787            flags: parseSampleFlags_1(data.subarray(offset, offset + 4))
48788          };
48789          offset += 4;
48790
48791          if (sampleDurationPresent) {
48792            sample.duration = view.getUint32(offset);
48793            offset += 4;
48794          }
48795
48796          if (sampleSizePresent) {
48797            sample.size = view.getUint32(offset);
48798            offset += 4;
48799          }
48800
48801          if (sampleCompositionTimeOffsetPresent) {
48802            if (result.version === 1) {
48803              sample.compositionTimeOffset = view.getInt32(offset);
48804            } else {
48805              sample.compositionTimeOffset = view.getUint32(offset);
48806            }
48807
48808            offset += 4;
48809          }
48810
48811          result.samples.push(sample);
48812          sampleCount--;
48813        }
48814
48815        while (sampleCount--) {
48816          sample = {};
48817
48818          if (sampleDurationPresent) {
48819            sample.duration = view.getUint32(offset);
48820            offset += 4;
48821          }
48822
48823          if (sampleSizePresent) {
48824            sample.size = view.getUint32(offset);
48825            offset += 4;
48826          }
48827
48828          if (sampleFlagsPresent) {
48829            sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4));
48830            offset += 4;
48831          }
48832
48833          if (sampleCompositionTimeOffsetPresent) {
48834            if (result.version === 1) {
48835              sample.compositionTimeOffset = view.getInt32(offset);
48836            } else {
48837              sample.compositionTimeOffset = view.getUint32(offset);
48838            }
48839
48840            offset += 4;
48841          }
48842
48843          result.samples.push(sample);
48844        }
48845
48846        return result;
48847      };
48848
48849      var parseTrun = trun$1;
48850
48851      var tfhd = function tfhd(data) {
48852        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
48853            result = {
48854          version: data[0],
48855          flags: new Uint8Array(data.subarray(1, 4)),
48856          trackId: view.getUint32(4)
48857        },
48858            baseDataOffsetPresent = result.flags[2] & 0x01,
48859            sampleDescriptionIndexPresent = result.flags[2] & 0x02,
48860            defaultSampleDurationPresent = result.flags[2] & 0x08,
48861            defaultSampleSizePresent = result.flags[2] & 0x10,
48862            defaultSampleFlagsPresent = result.flags[2] & 0x20,
48863            durationIsEmpty = result.flags[0] & 0x010000,
48864            defaultBaseIsMoof = result.flags[0] & 0x020000,
48865            i;
48866        i = 8;
48867
48868        if (baseDataOffsetPresent) {
48869          i += 4; // truncate top 4 bytes
48870          // FIXME: should we read the full 64 bits?
48871
48872          result.baseDataOffset = view.getUint32(12);
48873          i += 4;
48874        }
48875
48876        if (sampleDescriptionIndexPresent) {
48877          result.sampleDescriptionIndex = view.getUint32(i);
48878          i += 4;
48879        }
48880
48881        if (defaultSampleDurationPresent) {
48882          result.defaultSampleDuration = view.getUint32(i);
48883          i += 4;
48884        }
48885
48886        if (defaultSampleSizePresent) {
48887          result.defaultSampleSize = view.getUint32(i);
48888          i += 4;
48889        }
48890
48891        if (defaultSampleFlagsPresent) {
48892          result.defaultSampleFlags = view.getUint32(i);
48893        }
48894
48895        if (durationIsEmpty) {
48896          result.durationIsEmpty = true;
48897        }
48898
48899        if (!baseDataOffsetPresent && defaultBaseIsMoof) {
48900          result.baseDataOffsetIsMoof = true;
48901        }
48902
48903        return result;
48904      };
48905
48906      var parseTfhd = tfhd;
48907      var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
48908      var CaptionStream$1 = captionStream.CaptionStream;
48909      /**
48910        * Maps an offset in the mdat to a sample based on the the size of the samples.
48911        * Assumes that `parseSamples` has been called first.
48912        *
48913        * @param {Number} offset - The offset into the mdat
48914        * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
48915        * @return {?Object} The matching sample, or null if no match was found.
48916        *
48917        * @see ISO-BMFF-12/2015, Section 8.8.8
48918       **/
48919
48920      var mapToSample = function mapToSample(offset, samples) {
48921        var approximateOffset = offset;
48922
48923        for (var i = 0; i < samples.length; i++) {
48924          var sample = samples[i];
48925
48926          if (approximateOffset < sample.size) {
48927            return sample;
48928          }
48929
48930          approximateOffset -= sample.size;
48931        }
48932
48933        return null;
48934      };
48935      /**
48936        * Finds SEI nal units contained in a Media Data Box.
48937        * Assumes that `parseSamples` has been called first.
48938        *
48939        * @param {Uint8Array} avcStream - The bytes of the mdat
48940        * @param {Object[]} samples - The samples parsed out by `parseSamples`
48941        * @param {Number} trackId - The trackId of this video track
48942        * @return {Object[]}
48942 seiNals - the parsed SEI NALUs found.
48943        *   The contents of the seiNal should match what is expected by
48944        *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
48945        *
48946        * @see ISO-BMFF-12/2015, Section 8.1.1
48947        * @see Rec. ITU-T H.264, 7.3.2.3.1
48948       **/
48949
48950
48951      var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
48952        var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
48953            result = [],
48954            seiNal,
48955            i,
48956            length,
48957            lastMatchedSample;
48958
48959        for (i = 0; i + 4 < avcStream.length; i += length) {
48960          length = avcView.getUint32(i);
48961          i += 4; // Bail if this doesn't appear to be an H264 stream
48962
48963          if (length <= 0) {
48964            continue;
48965          }
48966
48967          switch (avcStream[i] & 0x1F) {
48968            case 0x06:
48969              var data = avcStream.subarray(i + 1, i + 1 + length);
48970              var matchingSample = mapToSample(i, samples);
48971              seiNal = {
48972                nalUnitType: 'sei_rbsp',
48973                size: length,
48974                data: data,
48975                escapedRBSP: discardEmulationPreventionBytes$1(data),
48976                trackId: trackId
48977              };
48978
48979              if (matchingSample) {
48980                seiNal.pts = matchingSample.pts;
48981                seiNal.dts = matchingSample.dts;
48982                lastMatchedSample = matchingSample;
48983              } else if (lastMatchedSample) {
48984                // If a matching sample cannot be found, use the last
48985                // sample's values as they should be as close as possible
48986                seiNal.pts = lastMatchedSample.pts;
48987                seiNal.dts = lastMatchedSample.dts;
48988              } else {
48989                // eslint-disable-next-line no-console
48990                console.log("We've encountered a nal unit without data. See mux.js#233.");
48991                break;
48992              }
48993
48994              result.push(seiNal);
48995              break;
48996          }
48997        }
48998
48999        return result;
49000      };
49001      /**
49002        * Parses sample information out of Track Run Boxes and calculates
49003        * the absolute presentation and decode timestamps of each sample.
49004        *
49005        * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
49006        * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
49007            @see ISO-BMFF-12/2015, Section 8.8.12
49008        * @param {Object} tfhd - The parsed Track Fragment Header
49009        *   @see inspect.parseTfhd
49010        * @return {Object[]} the parsed samples
49011        *
49012        * @see ISO-BMFF-12/2015, Section 8.8.8
49013       **/
49014
49015
49016      var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
49017        var currentDts = baseMediaDecodeTime;
49018        var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
49019        var defaultSampleSize = tfhd.defaultSampleSize || 0;
49020        var trackId = tfhd.trackId;
49021        var allSamples = [];
49022        truns.forEach(function (trun) {
49023          // Note: We currently do not parse the sample table as well
49024          // as the trun. It's possible some sources will require this.
49025          // moov > trak > mdia > minf > stbl
49026          var trackRun = parseTrun(trun);
49027          var samples = trackRun.samples;
49028          samples.forEach(function (sample) {
49029            if (sample.duration === undefined) {
49030              sample.duration = defaultSampleDuration;
49031            }
49032
49033            if (sample.size === undefined) {
49034              sample.size = defaultSampleSize;
49035            }
49036
49037            sample.trackId = trackId;
49038            sample.dts = currentDts;
49039
49040            if (sample.compositionTimeOffset === undefined) {
49041              sample.compositionTimeOffset = 0;
49042            }
49043
49044            sample.pts = currentDts + sample.compositionTimeOffset;
49045            currentDts += sample.duration;
49046          });
49047          allSamples = allSamples.concat(samples);
49048        });
49049        return allSamples;
49050      };
49051      /**
49052        * Parses out caption nals from an FMP4 segment's video tracks.
49053        *
49054        * @param {Uint8Array} segment - The bytes of a single segment
49055        * @param {Number} videoTrackId - The trackId of a video track in the segment
49056        * @return {Object.<Number, Object[]>} A mapping of video trackId to
49057        *   a list of seiNals found in that track
49058       **/
49059
49060
49061      var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
49062        // To get the samples
49063        var trafs = findBox_1(segment, ['moof', 'traf']); // To get SEI NAL units
49064
49065        var mdats = findBox_1(segment, ['mdat']);
49066        var captionNals = {};
49067        var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
49068
vendor: 3,962 bytes, lines 49069-49171
49069        mdats.forEach(function (mdat, index) {
49070          var matchingTraf = trafs[index];
49071          mdatTrafPairs.push({
49072            mdat: mdat,
49073            traf: matchingTraf
49074          });
49075        });
49076        mdatTrafPairs.forEach(function (pair) {
49077          var mdat = pair.mdat;
49078          var traf = pair.traf;
49079          var tfhd = findBox_1(traf, ['tfhd']); // Exactly 1 tfhd per traf
49080
49081          var headerInfo = parseTfhd(tfhd[0]);
49082          var trackId = headerInfo.trackId;
49083          var tfdt = findBox_1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
49084
49085          var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
49086          var truns = findBox_1(traf, ['trun']);
49087          var samples;
49088          var seiNals; // Only parse video data for the chosen video track
49089
49090          if (videoTrackId === trackId && truns.length > 0) {
49091            samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
49092            seiNals = findSeiNals(mdat, samples, trackId);
49093
49094            if (!captionNals[trackId]) {
49095              captionNals[trackId] = [];
49096            }
49097
49098            captionNals[trackId] = captionNals[trackId].concat(seiNals);
49099          }
49100        });
49101        return captionNals;
49102      };
49103      /**
49104        * Parses out inband captions from an MP4 container and returns
49105        * caption objects that can be used by WebVTT and the TextTrack API.
49106        * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
49107        * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
49108        * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
49109        *
49110        * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
49111        * @param {Number} trackId - The id of the video track to parse
49112        * @param {Number} timescale - The timescale for the video track from the init segment
49113        *
49114        * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
49115        * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
49116        * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
49117        * @return {String} parsedCaptions[].text - The visible content of the caption
49118       **/
49119
49120
49121      var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
49122        var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
49123
49124        if (trackId === null) {
49125          return null;
49126        }
49127
49128        seiNals = parseCaptionNals(segment, trackId);
49129        return {
49130          seiNals: seiNals[trackId],
49131          timescale: timescale
49132        };
49133      };
49134      /**
49135        * Converts SEI NALUs into captions that can be used by video.js
49136       **/
49137
49138
49139      var CaptionParser = function CaptionParser() {
49140        var isInitialized = false;
49141        var captionStream; // Stores segments seen before trackId and timescale are set
49142
49143        var segmentCache; // Stores video track ID of the track being parsed
49144
49145        var trackId; // Stores the timescale of the track being parsed
49146
49147        var timescale; // Stores captions parsed so far
49148
49149        var parsedCaptions; // Stores whether we are receiving partial data or not
49150
49151        var parsingPartial;
49152        /**
49153          * A method to indicate whether a CaptionParser has been initalized
49154          * @returns {Boolean}
49155         **/
49156
49157        this.isInitialized = function () {
49158          return isInitialized;
49159        };
49160        /**
49161          * Initializes the underlying CaptionStream, SEI NAL parsing
49162          * and management, and caption collection
49163         **/
49164
49165
49166        this.init = function (options) {
49167          captionStream = new CaptionStream$1();
49168          isInitialized = true;
49169          parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
49170
49171          captionStream.on('data', function (event) {
vendor: 4,888 bytes, lines 49172-49321
49172            // Convert to seconds in the source's timescale
49173            event.startTime = event.startPts / timescale;
49174            event.endTime = event.endPts / timescale;
49175            parsedCaptions.captions.push(event);
49176            parsedCaptions.captionStreams[event.stream] = true;
49177          });
49178        };
49179        /**
49180          * Determines if a new video track will be selected
49181          * or if the timescale changed
49182          * @return {Boolean}
49183         **/
49184
49185
49186        this.isNewInit = function (videoTrackIds, timescales) {
49187          if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
49188            return false;
49189          }
49190
49191          return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
49192        };
49193        /**
49194          * Parses out SEI captions and interacts with underlying
49195          * CaptionStream to return dispatched captions
49196          *
49197          * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
49198          * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
49199          * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
49200          * @see parseEmbeddedCaptions
49201          * @see m2ts/caption-stream.js
49202         **/
49203
49204
49205        this.parse = function (segment, videoTrackIds, timescales) {
49206          var parsedData;
49207
49208          if (!this.isInitialized()) {
49209            return null; // This is not likely to be a video segment
49210          } else if (!videoTrackIds || !timescales) {
49211            return null;
49212          } else if (this.isNewInit(videoTrackIds, timescales)) {
49213            // Use the first video track only as there is no
49214            // mechanism to switch to other video tracks
49215            trackId = videoTrackIds[0];
49216            timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
49217            // data until we have one.
49218            // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
49219          } else if (trackId === null || !timescale) {
49220            segmentCache.push(segment);
49221            return null;
49222          } // Now that a timescale and trackId is set, parse cached segments
49223
49224
49225          while (segmentCache.length > 0) {
49226            var cachedSegment = segmentCache.shift();
49227            this.parse(cachedSegment, videoTrackIds, timescales);
49228          }
49229
49230          parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
49231
49232          if (parsedData === null || !parsedData.seiNals) {
49233            return null;
49234          }
49235
49236          this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
49237
49238          this.flushStream();
49239          return parsedCaptions;
49240        };
49241        /**
49242          * Pushes SEI NALUs onto CaptionStream
49243          * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
49244          * Assumes that `parseCaptionNals` has been called first
49245          * @see m2ts/caption-stream.js
49246          **/
49247
49248
49249        this.pushNals = function (nals) {
49250          if (!this.isInitialized() || !nals || nals.length === 0) {
49251            return null;
49252          }
49253
49254          nals.forEach(function (nal) {
49255            captionStream.push(nal);
49256          });
49257        };
49258        /**
49259          * Flushes underlying CaptionStream to dispatch processed, displayable captions
49260          * @see m2ts/caption-stream.js
49261         **/
49262
49263
49264        this.flushStream = function () {
49265          if (!this.isInitialized()) {
49266            return null;
49267          }
49268
49269          if (!parsingPartial) {
49270            captionStream.flush();
49271          } else {
49272            captionStream.partialFlush();
49273          }
49274        };
49275        /**
49276          * Reset caption buckets for new data
49277         **/
49278
49279
49280        this.clearParsedCaptions = function () {
49281          parsedCaptions.captions = [];
49282          parsedCaptions.captionStreams = {};
49283        };
49284        /**
49285          * Resets underlying CaptionStream
49286          * @see m2ts/caption-stream.js
49287         **/
49288
49289
49290        this.resetCaptionStream = function () {
49291          if (!this.isInitialized()) {
49292            return null;
49293          }
49294
49295          captionStream.reset();
49296        };
49297        /**
49298          * Convenience method to clear all captions flushed from the
49299          * CaptionStream and still being parsed
49300          * @see m2ts/caption-stream.js
49301         **/
49302
49303
49304        this.clearAllCaptions = function () {
49305          this.clearParsedCaptions();
49306          this.resetCaptionStream();
49307        };
49308        /**
49309          * Reset caption parser
49310         **/
49311
49312
49313        this.reset = function () {
49314          segmentCache = [];
49315          trackId = null;
49316          timescale = null;
49317
49318          if (!parsedCaptions) {
49319            parsedCaptions = {
49320              captions: [],
49321              // CC1, CC2, CC3, CC4
49322              captionStreams: {}
49323            };
49324          } else {
49325            this.clearParsedCaptions();
49326          }
49327
49328          this.resetCaptionStream();
49329        };
49330
49331        this.reset();
49332      };
49333
49334      var captionParser = CaptionParser;
49335      /* global self */
49336
49337      var typeFromStreamString = function typeFromStreamString(streamString) {
49338        if (streamString === 'AudioSegmentStream') {
49339          return 'audio';
49340        }
49341
49342        return streamString === 'VideoSegmentStream' ? 'video' : '';
49343      };
49344      /**
49345       * Re-emits transmuxer events by converting them into messages to the
49346       * world outside the worker.
49347       *
49348       * @param {Object} transmuxer the transmuxer to wire events on
49349       * @private
49350       */
49351
49352
49353      var wireFullTransmuxerEvents = function wireFullTransmuxerEvents(self, transmuxer) {
49354        transmuxer.on('data', function (segment) {
49355          // transfer ownership of the underlying ArrayBuffer
49356          // instead of doing a copy to save memory
49357          // ArrayBuffers are transferable but generic TypedArrays are not
49358          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
49359          var initArray = segment.initSegment;
49360          segment.initSegment = {
49361            data: initArray.buffer,
49362            byteOffset: initArray.byteOffset,
49363            byteLength: initArray.byteLength
49364          };
49365          var typedArray = segment.data;
49366          segment.data = typedArray.buffer;
49367          self.postMessage({
49368            action: 'data',
49369            segment: segment,
49370            byteOffset: typedArray.byteOffset,
49371            byteLength: typedArray.byteLength
49372          }, [segment.data]);
49373        });
49374        transmuxer.on('done', function (data) {
49375          self.postMessage({
49376            action: 'done'
49377          });
49378        });
49379        transmuxer.on('gopInfo', function (gopInfo) {
49380          self.postMessage({
49381            action: 'gopInfo',
49382            gopInfo: gopInfo
49383          });
49384        });
49385        transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
49386          var videoSegmentTimingInfo = {
49387            start: {
49388              decode: clock_4(timingInfo.start.dts),
49389              presentation: clock_4(timingInfo.start.pts)
49390            },
49391            end: {
49392              decode: clock_4(timingInfo.end.dts),
49393              presentation: clock_4(timingInfo.end.pts)
49394            },
49395            baseMediaDecodeTime: clock_4(timingInfo.baseMediaDecodeTime)
49396          };
49397
49398          if (timingInfo.prependedContentDuration) {
49399            videoSegmentTimingInfo.prependedContentDuration = clock_4(timingInfo.prependedContentDuration);
49400          }
49401
49402          self.postMessage({
49403            action: 'videoSegmentTimingInfo',
49404            videoSegmentTimingInfo: videoSegmentTimingInfo
49405          });
49406        });
49407        transmuxer.on('id3Frame', function (id3Frame) {
49408          self.postMessage({
49409            action: 'id3Frame',
49410            id3Frame: id3Frame
49411          });
49412        });
49413        transmuxer.on('caption', function (caption) {
49414          self.postMessage({
49415            action: 'caption',
49416            caption: caption
49417          });
49418        });
49419        transmuxer.on('trackinfo', function (trackInfo) {
49420          self.postMessage({
49421            action: 'trackinfo',
49422            trackInfo: trackInfo
49423          });
49424        });
49425        transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
49426          // convert to video TS since we prioritize video time over audio
49427          self.postMessage({
49428            action: 'audioTimingInfo',
49429            audioTimingInfo: {
49430              start: clock_4(audioTimingInfo.start),
49431              end: clock_4(audioTimingInfo.end)
49432            }
49433          });
49434        });
49435        transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
49436          self.postMessage({
49437            action: 'videoTimingInfo',
49438            videoTimingInfo: {
49439              start: clock_4(videoTimingInfo.start),
49440              end: clock_4(videoTimingInfo.end)
49441            }
49442          });
49443        });
49444      };
49445
49446      var wirePartialTransmuxerEvents = function wirePartialTransmuxerEvents(self, transmuxer) {
49447        transmuxer.on('data', function (event) {
49448          // transfer ownership of the underlying ArrayBuffer
49449          // instead of doing a copy to save memory
49450          // ArrayBuffers are transferable but generic TypedArrays are not
49451          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
49452          var initSegment = {
49453            data: event.data.track.initSegment.buffer,
49454            byteOffset: event.data.track.initSegment.byteOffset,
49455            byteLength: event.data.track.initSegment.byteLength
49456          };
49457          var boxes = {
49458            data: event.data.boxes.buffer,
49459            byteOffset: event.data.boxes.byteOffset,
49460            byteLength: event.data.boxes.byteLength
49461          };
49462          var segment = {
49463            boxes: boxes,
49464            initSegment: initSegment,
49465            type: event.type,
49466            sequence: event.data.sequence
49467          };
49468
49469          if (typeof event.data.videoFrameDts !== 'undefined') {
49470            segment.videoFrameDtsTime = clock_4(event.data.videoFrameDts);
49471          }
49472
49473          if (typeof event.data.videoFramePts !== 'undefined') {
49474            segment.videoFramePtsTime = clock_4(event.data.videoFramePts);
49475          }
49476
49477          self.postMessage({
49478            action: 'data',
49479            segment: segment
49480          }, [segment.boxes.data, segment.initSegment.data]);
49481        });
49482        transmuxer.on('id3Frame', function (id3Frame) {
49483          self.postMessage({
49484            action: 'id3Frame',
49485            id3Frame: id3Frame
49486          });
49487        });
49488        transmuxer.on('caption', function (caption) {
49489          self.postMessage({
49490            action: 'caption',
49491            caption: caption
49492          });
49493        });
49494        transmuxer.on('done', function (data) {
49495          self.postMessage({
49496            action: 'done',
49497            type: typeFromStreamString(data)
49498          });
49499        });
49500        transmuxer.on('partialdone', function (data) {
49501          self.postMessage({
49502            action: 'partialdone',
49503            type: typeFromStreamString(data)
49504          });
49505        });
49506        transmuxer.on('endedsegment', function (data) {
49507          self.postMessage({
49508            action: 'endedSegment',
49509            type: typeFromStreamString(data)
49510          });
49511        });
49512        transmuxer.on('trackinfo', function (trackInfo) {
49513          self.postMessage({
49514            action: 'trackinfo',
49515            trackInfo: trackInfo
49516          });
49517        });
49518        transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
49519          // This can happen if flush is called when no
49520          // audio has been processed. This should be an
49521          // unusual case, but if it does occur should not
49522          // result in valid data being returned
49523          if (audioTimingInfo.start === null) {
49524            self.postMessage({
49525              action: 'audioTimingInfo',
49526              audioTimingInfo: audioTimingInfo
49527            });
49528            return;
49529          } // convert to video TS since we prioritize video time over audio
49530
49531
49532          var timingInfoInSeconds = {
49533            start: clock_4(audioTimingInfo.start)
49534          };
49535
49536          if (audioTimingInfo.end) {
49537            timingInfoInSeconds.end = clock_4(audioTimingInfo.end);
49538          }
49539
49540          self.postMessage({
49541            action: 'audioTimingInfo',
49542            audioTimingInfo: timingInfoInSeconds
49543          });
49544        });
49545        transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
49546          var timingInfoInSeconds = {
49547            start: clock_4(videoTimingInfo.start)
49548          };
49549
49550          if (videoTimingInfo.end) {
49551            timingInfoInSeconds.end = clock_4(videoTimingInfo.end);
49552          }
49553
49554          self.postMessage({
49555            action: 'videoTimingInfo',
49556            videoTimingInfo: timingInfoInSeconds
49557          });
49558        });
49559      };
49560      /**
49561       * All incoming messages route through this hash. If no function exists
49562       * to handle an incoming message, then we ignore the message.
49563       *
49564       * @class MessageHandlers
49565       * @param {Object} options the options to initialize with
49566       */
49567
49568
49569      var MessageHandlers = /*#__PURE__*/function () {
49570        function MessageHandlers(self, options) {
49571          this.options = options || {};
49572          this.self = self;
49573          this.init();
49574        }
49575        /**
49576         * initialize our web worker and wire all the events.
49577         */
49578
49579
49580        var _proto = MessageHandlers.prototype;
49581
49582        _proto.init = function init() {
49583          if (this.transmuxer) {
49584            this.transmuxer.dispose();
49585          }
49586
49587          this.transmuxer = this.options.handlePartialData ? new transmuxer$1(this.options) : new transmuxer_1(this.options);
49588
49589          if (this.options.handlePartialData) {
49590            wirePartialTransmuxerEvents(this.self, this.transmuxer);
49591          } else {
49592            wireFullTransmuxerEvents(this.self, this.transmuxer);
49593          }
49594        };
49595
49596        _proto.pushMp4Captions = function pushMp4Captions(data) {
49597          if (!this.captionParser) {
49598            this.captionParser = new captionParser();
49599            this.captionParser.init();
49600          }
49601
49602          var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
49603          var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
49604          this.self.postMessage({
49605            action: 'mp4Captions',
49606            captions: parsed && parsed.captions || [],
49607            data: segment.buffer
49608          }, [segment.buffer]);
49609        };
49610
49611        _proto.clearAllMp4Captions = function clearAllMp4Captions() {
49612          if (this.captionParser) {
49613            this.captionParser.clearAllCaptions();
49614          }
49615        };
49616
49617        _proto.clearParsedMp4Captions = function clearParsedMp4Captions() {
49618          if (this.captionParser) {
49619            this.captionParser.clearParsedCaptions();
49620          }
49621        }
49622        /**
49623         * Adds data (a ts segment) to the start of the transmuxer pipeline for
49624         * processing.
49625         *
49626         * @param {ArrayBuffer} data data to push into the muxer
49627         */
49628        ;
49629
49630        _proto.push = function push(data) {
49631          // Cast array buffer to correct type for transmuxer
49632          var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
49633          this.transmuxer.push(segment);
49634        }
49635        /**
49636         * Recreate the transmuxer so that the next segment added via `push`
49637         * start with a fresh transmuxer.
49638         */
49639        ;
49640
49641        _proto.reset = function reset() {
49642          this.transmuxer.reset();
49643        }
49644        /**
49645         * Set the value that will be used as the `baseMediaDecodeTime` time for the
49646         * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
49647         * set relative to the first based on the PTS values.
49648         *
49649         * @param {Object} data used to set the timestamp offset in the muxer
49650         */
49651        ;
49652
49653        _proto.setTimestampOffset = function setTimestampOffset(data) {
49654          var timestampOffset = data.timestampOffset || 0;
49655          this.transmuxer.setBaseMediaDecodeTime(Math.round(clock_2(timestampOffset)));
49656        };
49657
49658        _proto.setAudioAppendStart = function setAudioAppendStart(data) {
49659          this.transmuxer.setAudioAppendStart(Math.ceil(clock_2(data.appendStart)));
49660        };
49661
49662        _proto.setRemux = function setRemux(data) {
49663          this.transmuxer.setRemux(data.remux);
49664        }
49665        /**
49666         * Forces the pipeline to finish processing the last segment and emit it's
49667         * results.
49668         *
49669         * @param {Object} data event data, not really used
49670         */
49671        ;
49672
49673        _proto.flush = function flush(data) {
49674          this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
49675
49676          self.postMessage({
49677            action: 'done',
49678            type: 'transmuxed'
49679          });
49680        };
49681
49682        _proto.partialFlush = function partialFlush(data) {
49683          this.transmuxer.partialFlush(); // transmuxed partialdone action is fired after both audio/video pipelines are flushed
49684
49685          self.postMessage({
49686            action: 'partialdone',
49687            type: 'transmuxed'
49688          });
49689        };
49690
49691        _proto.endTimeline = function endTimeline() {
49692          this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
49693          // timelines
49694
49695          self.postMessage({
49696            action: 'endedtimeline',
49697            type: 'transmuxed'
49698          });
49699        };
49700
49701        _proto.alignGopsWith = function alignGopsWith(data) {
49702          this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
49703        };
49704
49705        return MessageHandlers;
49706      }();
49707      /**
49708       * Our web worker interface so that things can talk to mux.js
49709       * that will be running in a web worker. the scope is passed to this by
49710       * webworkify.
49711       *
49712       * @param {Object} self the scope for the web worker
49713       */
49714
49715
49716      var TransmuxerWorker = function TransmuxerWorker(self) {
49717        self.onmessage = function (event) {
vendor: 38,075 bytes, lines 49718-50774
49718          if (event.data.action === 'init' && event.data.options) {
49719            this.messageHandlers = new MessageHandlers(self, event.data.options);
49720            return;
49721          }
49722
49723          if (!this.messageHandlers) {
49724            this.messageHandlers = new MessageHandlers(self);
49725          }
49726
49727          if (event.data && event.data.action && event.data.action !== 'init') {
49728            if (this.messageHandlers[event.data.action]) {
49729              this.messageHandlers[event.data.action](event.data);
49730            }
49731          }
49732        };
49733      };
49734
49735      var transmuxerWorker = new TransmuxerWorker(self);
49736      return transmuxerWorker;
49737    }();
49738  });
49739  /**
49740   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
49741   * codec strings, or translating codec strings into objects that can be examined.
49742   */
49743
49744  /**
49745   * Returns a set of codec strings parsed from the playlist or the default
49746   * codec strings if no codecs were specified in the playlist
49747   *
49748   * @param {Playlist} media the current media playlist
49749   * @return {Object} an object with the video and audio codecs
49750   */
49751
49752  var getCodecs = function getCodecs(media) {
49753    // if the codecs were explicitly specified, use them instead of the
49754    // defaults
49755    var mediaAttributes = media.attributes || {};
49756
49757    if (mediaAttributes.CODECS) {
49758      return codecs_10(mediaAttributes.CODECS);
49759    }
49760  };
49761
49762  var isMaat = function isMaat(master, media) {
49763    var mediaAttributes = media.attributes || {};
49764    return master && master.mediaGroups && master.mediaGroups.AUDIO && mediaAttributes.AUDIO && master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
49765  };
49766
49767  var isMuxed = function isMuxed(master, media) {
49768    if (!isMaat(master, media)) {
49769      return true;
49770    }
49771
49772    var mediaAttributes = media.attributes || {};
49773    var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
49774
49775    for (var groupId in audioGroup) {
49776      // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
49777      // or there are listed playlists (the case for DASH, as the manifest will have already
49778      // provided all of the details necessary to generate the audio playlist, as opposed to
49779      // HLS' externally requested playlists), then the content is demuxed.
49780      if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
49781        return true;
49782      }
49783    }
49784
49785    return false;
49786  };
49787  /**
49788   * Calculates the codec strings for a working configuration of
49789   * SourceBuffers to play variant streams in a master playlist. If
49790   * there is no possible working configuration, an empty object will be
49791   * returned.
49792   *
49793   * @param master {Object} the m3u8 object for the master playlist
49794   * @param media {Object} the m3u8 object for the variant playlist
49795   * @return {Object} the codec strings.
49796   *
49797   * @private
49798   */
49799
49800
49801  var codecsForPlaylist = function codecsForPlaylist(master, media) {
49802    var mediaAttributes = media.attributes || {};
49803    var codecInfo = getCodecs(media) || {}; // HLS with multiple-audio tracks must always get an audio codec.
49804    // Put another way, there is no way to have a video-only multiple-audio HLS!
49805
49806    if (isMaat(master, media) && !codecInfo.audio) {
49807      if (!isMuxed(master, media)) {
49808        // It is possible for codecs to be specified on the audio media group playlist but
49809        // not on the rendition playlist. This is mostly the case for DASH, where audio and
49810        // video are always separate (and separately specified).
49811        var defaultCodecs = codecs_4(master, mediaAttributes.AUDIO);
49812
49813        if (defaultCodecs) {
49814          codecInfo.audio = defaultCodecs.audio;
49815        }
49816      }
49817    }
49818
49819    var codecs = {};
49820
49821    if (codecInfo.video) {
49822      codecs.video = codecs_11("" + codecInfo.video.type + codecInfo.video.details);
49823    }
49824
49825    if (codecInfo.audio) {
49826      codecs.audio = codecs_11("" + codecInfo.audio.type + codecInfo.audio.details);
49827    }
49828
49829    return codecs;
49830  };
49831
49832  var logger = function logger(source) {
49833    if (videojs$1.log.debug) {
49834      return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + " >");
49835    }
49836
49837    return function () {};
49838  };
49839
49840  var logFn = logger('PlaylistSelector');
49841
49842  var representationToString = function representationToString(representation) {
49843    if (!representation || !representation.playlist) {
49844      return;
49845    }
49846
49847    var playlist = representation.playlist;
49848    return JSON.stringify({
49849      id: playlist.id,
49850      bandwidth: representation.bandwidth,
49851      width: representation.width,
49852      height: representation.height,
49853      codecs: playlist.attributes && playlist.attributes.CODECS || ''
49854    });
49855  }; // Utilities
49856
49857  /**
49858   * Returns the CSS value for the specified property on an element
49859   * using `getComputedStyle`. Firefox has a long-standing issue where
49860   * getComputedStyle() may return null when running in an iframe with
49861   * `display: none`.
49862   *
49863   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
49864   * @param {HTMLElement} el the htmlelement to work on
49865   * @param {string} the proprety to get the style for
49866   */
49867
49868
49869  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
49870    if (!el) {
49871      return '';
49872    }
49873
49874    var result = window$1.getComputedStyle(el);
49875
49876    if (!result) {
49877      return '';
49878    }
49879
49880    return result[property];
49881  };
49882  /**
49883   * Resuable stable sort function
49884   *
49885   * @param {Playlists} array
49886   * @param {Function} sortFn Different comparators
49887   * @function stableSort
49888   */
49889
49890
49891  var stableSort = function stableSort(array, sortFn) {
49892    var newArray = array.slice();
49893    array.sort(function (left, right) {
49894      var cmp = sortFn(left, right);
49895
49896      if (cmp === 0) {
49897        return newArray.indexOf(left) - newArray.indexOf(right);
49898      }
49899
49900      return cmp;
49901    });
49902  };
49903  /**
49904   * A comparator function to sort two playlist object by bandwidth.
49905   *
49906   * @param {Object} left a media playlist object
49907   * @param {Object} right a media playlist object
49908   * @return {number} Greater than zero if the bandwidth attribute of
49909   * left is greater than the corresponding attribute of right. Less
49910   * than zero if the bandwidth of right is greater than left and
49911   * exactly zero if the two are equal.
49912   */
49913
49914
49915  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
49916    var leftBandwidth;
49917    var rightBandwidth;
49918
49919    if (left.attributes.BANDWIDTH) {
49920      leftBandwidth = left.attributes.BANDWIDTH;
49921    }
49922
49923    leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
49924
49925    if (right.attributes.BANDWIDTH) {
49926      rightBandwidth = right.attributes.BANDWIDTH;
49927    }
49928
49929    rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
49930    return leftBandwidth - rightBandwidth;
49931  };
49932  /**
49933   * A comparator function to sort two playlist object by resolution (width).
49934   *
49935   * @param {Object} left a media playlist object
49936   * @param {Object} right a media playlist object
49937   * @return {number} Greater than zero if the resolution.width attribute of
49938   * left is greater than the corresponding attribute of right. Less
49939   * than zero if the resolution.width of right is greater than left and
49940   * exactly zero if the two are equal.
49941   */
49942
49943
49944  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
49945    var leftWidth;
49946    var rightWidth;
49947
49948    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
49949      leftWidth = left.attributes.RESOLUTION.width;
49950    }
49951
49952    leftWidth = leftWidth || window$1.Number.MAX_VALUE;
49953
49954    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
49955      rightWidth = right.attributes.RESOLUTION.width;
49956    }
49957
49958    rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
49959    // have the same media dimensions/ resolution
49960
49961    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
49962      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
49963    }
49964
49965    return leftWidth - rightWidth;
49966  };
49967  /**
49968   * Chooses the appropriate media playlist based on bandwidth and player size
49969   *
49970   * @param {Object} master
49971   *        Object representation of the master manifest
49972   * @param {number} playerBandwidth
49973   *        Current calculated bandwidth of the player
49974   * @param {number} playerWidth
49975   *        Current width of the player element (should account for the device pixel ratio)
49976   * @param {number} playerHeight
49977   *        Current height of the player element (should account for the device pixel ratio)
49978   * @param {boolean} limitRenditionByPlayerDimensions
49979   *        True if the player width and height should be used during the selection, false otherwise
49980   * @return {Playlist} the highest bitrate playlist less than the
49981   * currently detected bandwidth, accounting for some amount of
49982   * bandwidth variance
49983   */
49984
49985
49986  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
49987    var options = {
49988      bandwidth: playerBandwidth,
49989      width: playerWidth,
49990      height: playerHeight,
49991      limitRenditionByPlayerDimensions: limitRenditionByPlayerDimensions
49992    }; // convert the playlists to an intermediary representation to make comparisons easier
49993
49994    var sortedPlaylistReps = master.playlists.map(function (playlist) {
49995      var bandwidth;
49996      var width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
49997      var height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
49998      bandwidth = playlist.attributes.BANDWIDTH;
49999      bandwidth = bandwidth || window$1.Number.MAX_VALUE;
50000      return {
50001        bandwidth: bandwidth,
50002        width: width,
50003        height: height,
50004        playlist: playlist
50005      };
50006    });
50007    stableSort(sortedPlaylistReps, function (left, right) {
50008      return left.bandwidth - right.bandwidth;
50009    }); // filter out any playlists that have been excluded due to
50010    // incompatible configurations
50011
50012    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
50013      return !Playlist.isIncompatible(rep.playlist);
50014    }); // filter out any playlists that have been disabled manually through the representations
50015    // api or blacklisted temporarily due to playback errors.
50016
50017    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
50018      return Playlist.isEnabled(rep.playlist);
50019    });
50020
50021    if (!enabledPlaylistReps.length) {
50022      // if there are no enabled playlists, then they have all been blacklisted or disabled
50023      // by the user through the representations api. In this case, ignore blacklisting and
50024      // fallback to what the user wants by using playlists the user has not disabled.
50025      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
50026        return !Playlist.isDisabled(rep.playlist);
50027      });
50028    } // filter out any variant that has greater effective bitrate
50029    // than the current estimated bandwidth
50030
50031
50032    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
50033      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
50034    });
50035    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
50036    // and then taking the very first element
50037
50038    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
50039      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
50040    })[0]; // if we're not going to limit renditions by player size, make an early decision.
50041
50042    if (limitRenditionByPlayerDimensions === false) {
50043      var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
50044
50045      if (_chosenRep && _chosenRep.playlist) {
50046        var type = 'sortedPlaylistReps';
50047
50048        if (bandwidthBestRep) {
50049          type = 'bandwidthBestRep';
50050        }
50051
50052        if (enabledPlaylistReps[0]) {
50053          type = 'enabledPlaylistReps';
50054        }
50055
50056        logFn("choosing " + representationToString(_chosenRep) + " using " + type + " with options", options);
50057        return _chosenRep.playlist;
50058      }
50059
50060      logFn('could not choose a playlist with options', options);
50061      return null;
50062    } // filter out playlists without resolution information
50063
50064
50065    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
50066      return rep.width && rep.height;
50067    }); // sort variants by resolution
50068
50069    stableSort(haveResolution, function (left, right) {
50070      return left.width - right.width;
50071    }); // if we have the exact resolution as the player use it
50072
50073    var resolutionBestRepList = haveResolution.filter(function (rep) {
50074      return rep.width === playerWidth && rep.height === playerHeight;
50075    });
50076    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
50077
50078    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
50079      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
50080    })[0];
50081    var resolutionPlusOneList;
50082    var resolutionPlusOneSmallest;
50083    var resolutionPlusOneRep; // find the smallest variant that is larger than the player
50084    // if there is no match of exact resolution
50085
50086    if (!resolutionBestRep) {
50087      resolutionPlusOneList = haveResolution.filter(function (rep) {
50088        return rep.width > playerWidth || rep.height > playerHeight;
50089      }); // find all the variants have the same smallest resolution
50090
50091      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
50092        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
50093      }); // ensure that we also pick the highest bandwidth variant that
50094      // is just-larger-than the video player
50095
50096      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
50097      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
50098        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
50099      })[0];
50100    } // fallback chain of variants
50101
50102
50103    var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
50104
50105    if (chosenRep && chosenRep.playlist) {
50106      var _type = 'sortedPlaylistReps';
50107
50108      if (resolutionPlusOneRep) {
50109        _type = 'resolutionPlusOneRep';
50110      } else if (resolutionBestRep) {
50111        _type = 'resolutionBestRep';
50112      } else if (bandwidthBestRep) {
50113        _type = 'bandwidthBestRep';
50114      } else if (enabledPlaylistReps[0]) {
50115        _type = 'enabledPlaylistReps';
50116      }
50117
50118      logFn("choosing " + representationToString(chosenRep) + " using " + _type + " with options", options);
50119      return chosenRep.playlist;
50120    }
50121
50122    logFn('could not choose a playlist with options', options);
50123    return null;
50124  }; // Playlist Selectors
50125
50126  /**
50127   * Chooses the appropriate media playlist based on the most recent
50128   * bandwidth estimate and the player size.
50129   *
50130   * Expects to be called within the context of an instance of VhsHandler
50131   *
50132   * @return {Playlist} the highest bitrate playlist less than the
50133   * currently detected bandwidth, accounting for some amount of
50134   * bandwidth variance
50135   */
50136
50137
50138  var lastBandwidthSelector = function lastBandwidthSelector() {
50139    var pixelRatio = this.useDevicePixelRatio ? window$1.devicePixelRatio || 1 : 1;
50140    return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions);
50141  };
50142  /**
50143   * Chooses the appropriate media playlist based on the potential to rebuffer
50144   *
50145   * @param {Object} settings
50146   *        Object of information required to use this selector
50147   * @param {Object} settings.master
50148   *        Object representation of the master manifest
50149   * @param {number} settings.currentTime
50150   *        The current time of the player
50151   * @param {number} settings.bandwidth
50152   *        Current measured bandwidth
50153   * @param {number} settings.duration
50154   *        Duration of the media
50155   * @param {number} settings.segmentDuration
50156   *        Segment duration to be used in round trip time calculations
50157   * @param {number} settings.timeUntilRebuffer
50158   *        Time left in seconds until the player has to rebuffer
50159   * @param {number} settings.currentTimeline
50160   *        The current timeline segments are being loaded from
50161   * @param {SyncController} settings.syncController
50162   *        SyncController for determining if we have a sync point for a given playlist
50163   * @return {Object|null}
50164   *         {Object} return.playlist
50165   *         The highest bandwidth playlist with the least amount of rebuffering
50166   *         {Number} return.rebufferingImpact
50167   *         The amount of time in seconds switching to this playlist will rebuffer. A
50168   *         negative value means that switching will cause zero rebuffering.
50169   */
50170
50171
50172  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
50173    var master = settings.master,
50174        currentTime = settings.currentTime,
50175        bandwidth = settings.bandwidth,
50176        duration = settings.duration,
50177        segmentDuration = settings.segmentDuration,
50178        timeUntilRebuffer = settings.timeUntilRebuffer,
50179        currentTimeline = settings.currentTimeline,
50180        syncController = settings.syncController; // filter out any playlists that have been excluded due to
50181    // incompatible configurations
50182
50183    var compatiblePlaylists = master.playlists.filter(function (playlist) {
50184      return !Playlist.isIncompatible(playlist);
50185    }); // filter out any playlists that have been disabled manually through the representations
50186    // api or blacklisted temporarily due to playback errors.
50187
50188    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
50189
50190    if (!enabledPlaylists.length) {
50191      // if there are no enabled playlists, then they have all been blacklisted or disabled
50192      // by the user through the representations api. In this case, ignore blacklisting and
50193      // fallback to what the user wants by using playlists the user has not disabled.
50194      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
50195        return !Playlist.isDisabled(playlist);
50196      });
50197    }
50198
50199    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
50200    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
50201      var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
50202      // sync request first. This will double the request time
50203
50204      var numRequests = syncPoint ? 1 : 2;
50205      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
50206      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
50207      return {
50208        playlist: playlist,
50209        rebufferingImpact: rebufferingImpact
50210      };
50211    });
50212    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
50213      return estimate.rebufferingImpact <= 0;
50214    }); // Sort by bandwidth DESC
50215
50216    stableSort(noRebufferingPlaylists, function (a, b) {
50217      return comparePlaylistBandwidth(b.playlist, a.playlist);
50218    });
50219
50220    if (noRebufferingPlaylists.length) {
50221      return noRebufferingPlaylists[0];
50222    }
50223
50224    stableSort(rebufferingEstimates, function (a, b) {
50225      return a.rebufferingImpact - b.rebufferingImpact;
50226    });
50227    return rebufferingEstimates[0] || null;
50228  };
50229  /**
50230   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
50231   * one with video.  If no renditions with video exist, return the lowest audio rendition.
50232   *
50233   * Expects to be called within the context of an instance of VhsHandler
50234   *
50235   * @return {Object|null}
50236   *         {Object} return.playlist
50237   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
50238   *         exists pick the lowest audio rendition.
50239   */
50240
50241
50242  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
50243    var _this = this; // filter out any playlists that have been excluded due to
50244    // incompatible configurations or playback errors
50245
50246
50247    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
50248
50249    stableSort(playlists, function (a, b) {
50250      return comparePlaylistBandwidth(a, b);
50251    }); // Parse and assume that playlists with no video codec have no video
50252    // (this is not necessarily true, although it is generally true).
50253    //
50254    // If an entire manifest has no valid videos everything will get filtered
50255    // out.
50256
50257    var playlistsWithVideo = playlists.filter(function (playlist) {
50258      return !!codecsForPlaylist(_this.playlists.master, playlist).video;
50259    });
50260    return playlistsWithVideo[0] || null;
50261  };
50262  /**
50263   * @file text-tracks.js
50264   */
50265
50266  /**
50267   * Create captions text tracks on video.js if they do not exist
50268   *
50269   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
50270   * @param {Object} tech the video.js tech
50271   * @param {Object} captionStream the caption stream to create
50272   * @private
50273   */
50274
50275
50276  var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStream) {
50277    if (!inbandTextTracks[captionStream]) {
50278      tech.trigger({
50279        type: 'usage',
50280        name: 'vhs-608'
50281      });
50282      tech.trigger({
50283        type: 'usage',
50284        name: 'hls-608'
50285      });
50286      var track = tech.textTracks().getTrackById(captionStream);
50287
50288      if (track) {
50289        // Resuse an existing track with a CC# id because this was
50290        // very likely created by videojs-contrib-hls from information
50291        // in the m3u8 for us to use
50292        inbandTextTracks[captionStream] = track;
50293      } else {
50294        // Otherwise, create a track with the default `CC#` label and
50295        // without a language
50296        inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
50297          kind: 'captions',
50298          id: captionStream,
50299          label: captionStream
50300        }, false).track;
50301      }
50302    }
50303  };
50304  /**
50305   * Add caption text track data to a source handler given an array of captions
50306   *
50307   * @param {Object}
50308   *   @param {Object} inbandTextTracks the inband text tracks
50309   *   @param {number} timestampOffset the timestamp offset of the source buffer
50310   *   @param {Array} captionArray an array of caption data
50311   * @private
50312   */
50313
50314
50315  var addCaptionData = function addCaptionData(_ref) {
50316    var inbandTextTracks = _ref.inbandTextTracks,
50317        captionArray = _ref.captionArray,
50318        timestampOffset = _ref.timestampOffset;
50319
50320    if (!captionArray) {
50321      return;
50322    }
50323
50324    var Cue = window$1.WebKitDataCue || window$1.VTTCue;
50325    captionArray.forEach(function (caption) {
50326      var track = caption.stream;
50327      inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
50328    });
50329  };
50330  /**
50331   * Define properties on a cue for backwards compatability,
50332   * but warn the user that the way that they are using it
50333   * is depricated and will be removed at a later date.
50334   *
50335   * @param {Cue} cue the cue to add the properties on
50336   * @private
50337   */
50338
50339
50340  var deprecateOldCue = function deprecateOldCue(cue) {
50341    Object.defineProperties(cue.frame, {
50342      id: {
50343        get: function get() {
50344          videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
50345          return cue.value.key;
50346        }
50347      },
50348      value: {
50349        get: function get() {
50350          videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
50351          return cue.value.data;
50352        }
50353      },
50354      privateData: {
50355        get: function get() {
50356          videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
50357          return cue.value.data;
50358        }
50359      }
50360    });
50361  };
50362  /**
50363   * Add metadata text track data to a source handler given an array of metadata
50364   *
50365   * @param {Object}
50366   *   @param {Object} inbandTextTracks the inband text tracks
50367   *   @param {Array} metadataArray an array of meta data
50368   *   @param {number} timestampOffset the timestamp offset of the source buffer
50369   *   @param {number} videoDuration the duration of the video
50370   * @private
50371   */
50372
50373
50374  var addMetadata = function addMetadata(_ref2) {
50375    var inbandTextTracks = _ref2.inbandTextTracks,
50376        metadataArray = _ref2.metadataArray,
50377        timestampOffset = _ref2.timestampOffset,
50378        videoDuration = _ref2.videoDuration;
50379
50380    if (!metadataArray) {
50381      return;
50382    }
50383
50384    var Cue = window$1.WebKitDataCue || window$1.VTTCue;
50385    var metadataTrack = inbandTextTracks.metadataTrack_;
50386
50387    if (!metadataTrack) {
50388      return;
50389    }
50390
50391    metadataArray.forEach(function (metadata) {
50392      var time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
50393      // ignore this bit of metadata.
50394      // This likely occurs when you have an non-timed ID3 tag like TIT2,
50395      // which is the "Title/Songname/Content description" frame
50396
50397      if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
50398        return;
50399      }
50400
50401      metadata.frames.forEach(function (frame) {
50402        var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
50403        cue.frame = frame;
50404        cue.value = frame;
50405        deprecateOldCue(cue);
50406        metadataTrack.addCue(cue);
50407      });
50408    });
50409
50410    if (!metadataTrack.cues || !metadataTrack.cues.length) {
50411      return;
50412    } // Updating the metadeta cues so that
50413    // the endTime of each cue is the startTime of the next cue
50414    // the endTime of last cue is the duration of the video
50415
50416
50417    var cues = metadataTrack.cues;
50418    var cuesArray = []; // Create a copy of the TextTrackCueList...
50419    // ...disregarding cues with a falsey value
50420
50421    for (var i = 0; i < cues.length; i++) {
50422      if (cues[i]) {
50423        cuesArray.push(cues[i]);
50424      }
50425    } // Group cues by their startTime value
50426
50427
50428    var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
50429      var timeSlot = obj[cue.startTime] || [];
50430      timeSlot.push(cue);
50431      obj[cue.startTime] = timeSlot;
50432      return obj;
50433    }, {}); // Sort startTimes by ascending order
50434
50435    var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
50436      return Number(a) - Number(b);
50437    }); // Map each cue group's endTime to the next group's startTime
50438
50439    sortedStartTimes.forEach(function (startTime, idx) {
50440      var cueGroup = cuesGroupedByStartTime[startTime];
50441      var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
50442
50443      cueGroup.forEach(function (cue) {
50444        cue.endTime = nextTime;
50445      });
50446    });
50447  };
50448  /**
50449   * Create metadata text track on video.js if it does not exist
50450   *
50451   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
50452   * @param {string} dispatchType the inband metadata track dispatch type
50453   * @param {Object} tech the video.js tech
50454   * @private
50455   */
50456
50457
50458  var createMetadataTrackIfNotExists = function createMetadataTrackIfNotExists(inbandTextTracks, dispatchType, tech) {
50459    if (inbandTextTracks.metadataTrack_) {
50460      return;
50461    }
50462
50463    inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
50464      kind: 'metadata',
50465      label: 'Timed Metadata'
50466    }, false).track;
50467    inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
50468  };
50469  /**
50470   * Remove cues from a track on video.js.
50471   *
50472   * @param {Double} start start of where we should remove the cue
50473   * @param {Double} end end of where the we should remove the cue
50474   * @param {Object} track the text track to remove the cues from
50475   * @private
50476   */
50477
50478
50479  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
50480    var i;
50481    var cue;
50482
50483    if (!track) {
50484      return;
50485    }
50486
50487    if (!track.cues) {
50488      return;
50489    }
50490
50491    i = track.cues.length;
50492
50493    while (i--) {
50494      cue = track.cues[i]; // Remove any overlapping cue
50495
50496      if (cue.startTime >= start && cue.endTime <= end) {
50497        track.removeCue(cue);
50498      }
50499    }
50500  };
50501  /**
50502   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
50503   * front of current time.
50504   *
50505   * @param {Array} buffer
50506   *        The current buffer of gop information
50507   * @param {number} currentTime
50508   *        The current time
50509   * @param {Double} mapping
50510   *        Offset to map display time to stream presentation time
50511   * @return {Array}
50512   *         List of gops considered safe to append over
50513   */
50514
50515
50516  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
50517    if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
50518      return [];
50519    } // pts value for current time + 3 seconds to give a bit more wiggle room
50520
50521
50522    var currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1);
50523    var i;
50524
50525    for (i = 0; i < buffer.length; i++) {
50526      if (buffer[i].pts > currentTimePts) {
50527        break;
50528      }
50529    }
50530
50531    return buffer.slice(i);
50532  };
50533  /**
50534   * Appends gop information (timing and byteLength) received by the transmuxer for the
50535   * gops appended in the last call to appendBuffer
50536   *
50537   * @param {Array} buffer
50538   *        The current buffer of gop information
50539   * @param {Array} gops
50540   *        List of new gop information
50541   * @param {boolean} replace
50542   *        If true, replace the buffer with the new gop information. If false, append the
50543   *        new gop information to the buffer in the right location of time.
50544   * @return {Array}
50545   *         Updated list of gop information
50546   */
50547
50548
50549  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
50550    if (!gops.length) {
50551      return buffer;
50552    }
50553
50554    if (replace) {
50555      // If we are in safe append mode, then completely overwrite the gop buffer
50556      // with the most recent appeneded data. This will make sure that when appending
50557      // future segments, we only try to align with gops that are both ahead of current
50558      // time and in the last segment appended.
50559      return gops.slice();
50560    }
50561
50562    var start = gops[0].pts;
50563    var i = 0;
50564
50565    for (i; i < buffer.length; i++) {
50566      if (buffer[i].pts >= start) {
50567        break;
50568      }
50569    }
50570
50571    return buffer.slice(0, i).concat(gops);
50572  };
50573  /**
50574   * Removes gop information in buffer that overlaps with provided start and end
50575   *
50576   * @param {Array} buffer
50577   *        The current buffer of gop information
50578   * @param {Double} start
50579   *        position to start the remove at
50580   * @param {Double} end
50581   *        position to end the remove at
50582   * @param {Double} mapping
50583   *        Offset to map display time to stream presentation time
50584   */
50585
50586
50587  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
50588    var startPts = Math.ceil((start - mapping) * clock_1);
50589    var endPts = Math.ceil((end - mapping) * clock_1);
50590    var updatedBuffer = buffer.slice();
50591    var i = buffer.length;
50592
50593    while (i--) {
50594      if (buffer[i].pts <= endPts) {
50595        break;
50596      }
50597    }
50598
50599    if (i === -1) {
50600      // no removal because end of remove range is before start of buffer
50601      return updatedBuffer;
50602    }
50603
50604    var j = i + 1;
50605
50606    while (j--) {
50607      if (buffer[j].pts <= startPts) {
50608        break;
50609      }
50610    } // clamp remove range start to 0 index
50611
50612
50613    j = Math.max(j, 0);
50614    updatedBuffer.splice(j, i - j + 1);
50615    return updatedBuffer;
50616  };
50617
50618  var shallowEqual = function shallowEqual(a, b) {
50619    // if both are undefined
50620    // or one or the other is undefined
50621    // they are not equal
50622    if (!a && !b || !a && b || a && !b) {
50623      return false;
50624    } // they are the same object and thus, equal
50625
50626
50627    if (a === b) {
50628      return true;
50629    } // sort keys so we can make sure they have
50630    // all the same keys later.
50631
50632
50633    var akeys = Object.keys(a).sort();
50634    var bkeys = Object.keys(b).sort(); // different number of keys, not equal
50635
50636    if (akeys.length !== bkeys.length) {
50637      return false;
50638    }
50639
50640    for (var i = 0; i < akeys.length; i++) {
50641      var key = akeys[i]; // different sorted keys, not equal
50642
50643      if (key !== bkeys[i]) {
50644        return false;
50645      } // different values, not equal
50646
50647
50648      if (a[key] !== b[key]) {
50649        return false;
50650      }
50651    }
50652
50653    return true;
50654  };
50655
50656  var CHECK_BUFFER_DELAY = 500;
50657
50658  var finite = function finite(num) {
50659    return typeof num === 'number' && isFinite(num);
50660  };
50661
50662  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, trackInfo) {
50663    // Although these checks should most likely cover non 'main' types, for now it narrows
50664    // the scope of our checks.
50665    if (loaderType !== 'main' || !startingMedia || !trackInfo) {
50666      return null;
50667    }
50668
50669    if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
50670      return 'Neither audio nor video found in segment.';
50671    }
50672
50673    if (startingMedia.hasVideo && !trackInfo.hasVideo) {
50674      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.';
50675    }
50676
50677    if (!startingMedia.hasVideo && trackInfo.hasVideo) {
50678      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.';
50679    }
50680
50681    return null;
50682  };
50683  /**
50684   * Calculates a time value that is safe to remove from the back buffer without interrupting
50685   * playback.
50686   *
50687   * @param {TimeRange} seekable
50688   *        The current seekable range
50689   * @param {number} currentTime
50690   *        The current time of the player
50691   * @param {number} targetDuration
50692   *        The target duration of the current playlist
50693   * @return {number}
50694   *         Time that is safe to remove from the back buffer without interrupting playback
50695   */
50696
50697
50698  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) {
50699    // 30 seconds before the playhead provides a safe default for trimming.
50700    //
50701    // Choosing a reasonable default is particularly important for high bitrate content and
50702    // VOD videos/live streams with large windows, as the buffer may end up overfilled and
50703    // throw an APPEND_BUFFER_ERR.
50704    var trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
50705
50706    if (seekable.length) {
50707      // Some live playlists may have a shorter window of content than the full allowed back
50708      // buffer. For these playlists, don't save content that's no longer within the window.
50709      trimTime = Math.max(trimTime, seekable.start(0));
50710    } // Don't remove within target duration of the current time to avoid the possibility of
50711    // removing the GOP currently being played, as removing it can cause playback stalls.
50712
50713
50714    var maxTrimTime = currentTime - targetDuration;
50715    return Math.min(maxTrimTime, trimTime);
50716  };
50717
50718  var segmentInfoString = function segmentInfoString(segmentInfo) {
50719    var _segmentInfo$segment = segmentInfo.segment,
50720        start = _segmentInfo$segment.start,
50721        end = _segmentInfo$segment.end,
50722        _segmentInfo$playlist = segmentInfo.playlist,
50723        seq = _segmentInfo$playlist.mediaSequence,
50724        id = _segmentInfo$playlist.id,
50725        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
50726        segments = _segmentInfo$playlist2 === void 0 ? [] : _segmentInfo$playlist2,
50727        index = segmentInfo.mediaIndex,
50728        timeline = segmentInfo.timeline;
50729    return ["appending [" + index + "] of [" + seq + ", " + (seq + segments.length) + "] from playlist [" + id + "]", "[" + start + " => " + end + "] in timeline [" + timeline + "]"].join(' ');
50730  };
50731
50732  var timingInfoPropertyForMedia = function timingInfoPropertyForMedia(mediaType) {
50733    return mediaType + "TimingInfo";
50734  };
50735  /**
50736   * Returns the timestamp offset to use for the segment.
50737   *
50738   * @param {number} segmentTimeline
50739   *        The timeline of the segment
50740   * @param {number} currentTimeline
50741   *        The timeline currently being followed by the loader
50742   * @param {number} startOfSegment
50743   *        The estimated segment start
50744   * @param {TimeRange[]} buffered
50745   *        The loader's buffer
50746   * @param {boolean} overrideCheck
50747   *        If true, no checks are made to see if the timestamp offset value should be set,
50748   *        but sets it directly to a value.
50749   *
50750   * @return {number|null}
50751   *         Either a number representing a new timestamp offset, or null if the segment is
50752   *         part of the same timeline
50753   */
50754
50755
50756  var timestampOffsetForSegment = function timestampOffsetForSegment(_ref) {
50757    var segmentTimeline = _ref.segmentTimeline,
50758        currentTimeline = _ref.currentTimeline,
50759        startOfSegment = _ref.startOfSegment,
50760        buffered = _ref.buffered,
50761        overrideCheck = _ref.overrideCheck; // Check to see if we are crossing a discontinuity to see if we need to set the
50762    // timestamp offset on the transmuxer and source buffer.
50763    //
50764    // Previously, we changed the timestampOffset if the start of this segment was less than
50765    // the currently set timestampOffset, but this isn't desirable as it can produce bad
50766    // behavior, especially around long running live streams.
50767
50768    if (!overrideCheck && segmentTimeline === currentTimeline) {
50769      return null;
50770    } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
50771    // value uses the end of the last segment if it is available. While this value
50772    // should often be correct, it's better to rely on the buffered end, as the new
50773    // content post discontinuity should line up with the buffered end as if it were
50774    // time 0 for the new content.
vendor: 5,339 bytes, lines 50775-50874
50775
50776
50777    return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
50778  };
50779  /**
50780   * Returns whether or not the loader should wait for a timeline change from the timeline
50781   * change controller before processing the segment.
50782   *
50783   * Primary timing in VHS goes by video. This is different from most media players, as
50784   * audio is more often used as the primary timing source. For the foreseeable future, VHS
50785   * will continue to use video as the primary timing source, due to the current logic and
50786   * expectations built around it.
50787
50788   * Since the timing follows video, in order to maintain sync, the video loader is
50789   * responsible for setting both audio and video source buffer timestamp offsets.
50790   *
50791   * Setting different values for audio and video source buffers could lead to
50792   * desyncing. The following examples demonstrate some of the situations where this
50793   * distinction is important. Note that all of these cases involve demuxed content. When
50794   * content is muxed, the audio and video are packaged together, therefore syncing
50795   * separate media playlists is not an issue.
50796   *
50797   * CASE 1: Audio prepares to load a new timeline before video:
50798   *
50799   * Timeline:       0                 1
50800   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50801   * Audio Loader:                     ^
50802   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50803   * Video Loader              ^
50804   *
50805   * In the above example, the audio loader is preparing to load the 6th segment, the first
50806   * after a discontinuity, while the video loader is still loading the 5th segment, before
50807   * the discontinuity.
50808   *
50809   * If the audio loader goes ahead and loads and appends the 6th segment before the video
50810   * loader crosses the discontinuity, then when appended, the 6th audio segment will use
50811   * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
50812   * the audio loader must provide the audioAppendStart value to trim the content in the
50813   * transmuxer, and that value relies on the audio timestamp offset. Since the audio
50814   * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
50815   * segment until that value is provided.
50816   *
50817   * CASE 2: Video prepares to load a new timeline before audio:
50818   *
50819   * Timeline:       0                 1
50820   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50821   * Audio Loader:             ^
50822   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50823   * Video Loader                      ^
50824   *
50825   * In the above example, the video loader is preparing to load the 6th segment, the first
50826   * after a discontinuity, while the audio loader is still loading the 5th segment, before
50827   * the discontinuity.
50828   *
50829   * If the video loader goes ahead and loads and appends the 6th segment, then once the
50830   * segment is loaded and processed, both the video and audio timestamp offsets will be
50831   * set, since video is used as the primary timing source. This is to ensure content lines
50832   * up appropriately, as any modifications to the video timing are reflected by audio when
50833   * the video loader sets the audio and video timestamp offsets to the same value. However,
50834   * setting the timestamp offset for audio before audio has had a chance to change
50835   * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
50836   * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
50837   *
50838   * CASE 3: When seeking, audio prepares to load a new timeline before video
50839   *
50840   * Timeline:       0                 1
50841   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50842   * Audio Loader:           ^
50843   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
50844   * Video Loader            ^
50845   *
50846   * In the above example, both audio and video loaders are loading segments from timeline
50847   * 0, but imagine that the seek originated from timeline 1.
50848   *
50849   * When seeking to a new timeline, the timestamp offset will be set based on the expected
50850   * segment start of the loaded video segment. In order to maintain sync, the audio loader
50851   * must wait for the video loader to load its segment and update both the audio and video
50852   * timestamp offsets before it may load and append its own segment. This is the case
50853   * whether the seek results in a mismatched segment request (e.g., the audio loader
50854   * chooses to load segment 3 and the video loader chooses to load segment 4) or the
50855   * loaders choose to load the same segment index from each playlist, as the segments may
50856   * not be aligned perfectly, even for matching segment indexes.
50857   *
50858   * @param {Object} timelinechangeController
50859   * @param {number} currentTimeline
50860   *        The timeline currently being followed by the loader
50861   * @param {number} segmentTimeline
50862   *        The timeline of the segment being loaded
50863   * @param {('main'|'audio')} loaderType
50864   *        The loader type
50865   * @param {boolean} audioDisabled
50866   *        Whether the audio is disabled for the loader. This should only be true when the
50867   *        loader may have muxed audio in its segment, but should not append it, e.g., for
50868   *        the main loader when an alternate audio playlist is active.
50869   *
50870   * @return {boolean}
50871   *         Whether the loader should wait for a timeline change from the timeline change
50872   *         controller before processing the segment
50873   */
50874
50875
50876  var shouldWaitForTimelineChange = function shouldWaitForTimelineChange(_ref2) {
50877    var timelineChangeController = _ref2.timelineChangeController,
50878        currentTimeline = _ref2.currentTimeline,
50879        segmentTimeline = _ref2.segmentTimeline,
50880        loaderType = _ref2.loaderType,
50881        audioDisabled = _ref2.audioDisabled;
50882
50883    if (currentTimeline === segmentTimeline) {
50884      return false;
50885    }
50886
50887    if (loaderType === 'audio') {
50888      var lastMainTimelineChange = timelineChangeController.lastTimelineChange({
50889        type: 'main'
50890      }); // Audio loader should wait if:
50891      //
50892      // * main hasn't had a timeline change yet (thus has not loaded its first segment)
50893      // * main hasn't yet changed to the timeline audio is looking to load
50894
50895      return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
50896    } // The main loader only needs to wait for timeline changes if there's demuxed audio.
50897    // Otherwise, there's nothing to wait for, since audio would be muxed into the main
50898    // loader's segments (or the content is audio/video only and handled by the main
50899    // loader).
50900
50901
50902    if (loaderType === 'main' && audioDisabled) {
50903      var pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
50904        type: 'audio'
50905      }); // Main loader should wait for the audio loader if audio is not pending a timeline
50906      // change to the current timeline.
50907      //
50908      // Since the main loader is responsible for setting the timestamp offset for both
50909      // audio and video, the main loader must wait for audio to be about to change to its
50910      // timeline before setting the offset, otherwise, if audio is behind in loading,
50911      // segments from the previous timeline would be adjusted by the new timestamp offset.
50912      //
50913      // This requirement means that video will not cross a timeline until the audio is
50914      // about to cross to it, so that way audio and video will always cross the timeline
50915      // together.
50916      //
50917      // In addition to normal timeline changes, these rules also apply to the start of a
50918      // stream (going from a non-existent timeline, -1, to timeline 0). It's important
50919      // that these rules apply to the first timeline change because if they did not, it's
50920      // possible that the main loader will cross two timelines before the audio loader has
50921      // crossed one. Logic may be implemented to handle the startup as a special case, but
50922      // it's easier to simply treat all timeline changes the same.
50923
50924      if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
50925        return false;
vendor: 25,927 bytes, lines 50926-51749
50926      }
50927
50928      return true;
50929    }
50930
50931    return false;
50932  };
50933  /**
50934   * An object that manages segment loading and appending.
50935   *
50936   * @class SegmentLoader
50937   * @param {Object} options required and optional options
50938   * @extends videojs.EventTarget
50939   */
50940
50941
50942  var SegmentLoader = /*#__PURE__*/function (_videojs$EventTarget) {
50943    inheritsLoose(SegmentLoader, _videojs$EventTarget);
50944
50945    function SegmentLoader(settings, options) {
50946      var _this;
50947
50948      _this = _videojs$EventTarget.call(this) || this; // check pre-conditions
50949
50950      if (!settings) {
50951        throw new TypeError('Initialization settings are required');
50952      }
50953
50954      if (typeof settings.currentTime !== 'function') {
50955        throw new TypeError('No currentTime getter specified');
50956      }
50957
50958      if (!settings.mediaSource) {
50959        throw new TypeError('No MediaSource specified');
50960      } // public properties
50961
50962
50963      _this.bandwidth = settings.bandwidth;
50964      _this.throughput = {
50965        rate: 0,
50966        count: 0
50967      };
50968      _this.roundTrip = NaN;
50969
50970      _this.resetStats_();
50971
50972      _this.mediaIndex = null; // private settings
50973
50974      _this.hasPlayed_ = settings.hasPlayed;
50975      _this.currentTime_ = settings.currentTime;
50976      _this.seekable_ = settings.seekable;
50977      _this.seeking_ = settings.seeking;
50978      _this.duration_ = settings.duration;
50979      _this.mediaSource_ = settings.mediaSource;
50980      _this.vhs_ = settings.vhs;
50981      _this.loaderType_ = settings.loaderType;
50982      _this.currentMediaInfo_ = void 0;
50983      _this.startingMediaInfo_ = void 0;
50984      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
50985      _this.goalBufferLength_ = settings.goalBufferLength;
50986      _this.sourceType_ = settings.sourceType;
50987      _this.sourceUpdater_ = settings.sourceUpdater;
50988      _this.inbandTextTracks_ = settings.inbandTextTracks;
50989      _this.state_ = 'INIT';
50990      _this.handlePartialData_ = settings.handlePartialData;
50991      _this.timelineChangeController_ = settings.timelineChangeController;
50992      _this.shouldSaveSegmentTimingInfo_ = true; // private instance variables
50993
50994      _this.checkBufferTimeout_ = null;
50995      _this.error_ = void 0;
50996      _this.currentTimeline_ = -1;
50997      _this.pendingSegment_ = null;
50998      _this.xhrOptions_ = null;
50999      _this.pendingSegments_ = [];
51000      _this.audioDisabled_ = false;
51001      _this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
51002
51003      _this.gopBuffer_ = [];
51004      _this.timeMapping_ = 0;
51005      _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
51006      _this.appendInitSegment_ = {
51007        audio: true,
51008        video: true
51009      };
51010      _this.playlistOfLastInitSegment_ = {
51011        audio: null,
51012        video: null
51013      };
51014      _this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
51015      // information yet to start the loading process (e.g., if the audio loader wants to
51016      // load a segment from the next timeline but the main loader hasn't yet crossed that
51017      // timeline), then the load call will be added to the queue until it is ready to be
51018      // processed.
51019
51020      _this.loadQueue_ = [];
51021      _this.metadataQueue_ = {
51022        id3: [],
51023        caption: []
51024      }; // Fragmented mp4 playback
51025
51026      _this.activeInitSegmentId_ = null;
51027      _this.initSegments_ = {}; // HLSe playback
51028
51029      _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
51030      _this.keyCache_ = {};
51031      _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
51032      // between a time in the display time and a segment index within
51033      // a playlist
51034
51035      _this.syncController_ = settings.syncController;
51036      _this.syncPoint_ = {
51037        segmentIndex: 0,
51038        time: 0
51039      };
51040      _this.transmuxer_ = _this.createTransmuxer_();
51041
51042      _this.triggerSyncInfoUpdate_ = function () {
51043        return _this.trigger('syncinfoupdate');
51044      };
51045
51046      _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_);
51047
51048      _this.mediaSource_.addEventListener('sourceopen', function () {
51049        if (!_this.isEndOfStream_()) {
51050          _this.ended_ = false;
51051        }
51052      }); // ...for determining the fetch location
51053
51054
51055      _this.fetchAtBuffer_ = false;
51056      _this.logger_ = logger("SegmentLoader[" + _this.loaderType_ + "]");
51057      Object.defineProperty(assertThisInitialized(_this), 'state', {
51058        get: function get() {
51059          return this.state_;
51060        },
51061        set: function set(newState) {
51062          if (newState !== this.state_) {
51063            this.logger_(this.state_ + " -> " + newState);
51064            this.state_ = newState;
51065            this.trigger('statechange');
51066          }
51067        }
51068      });
51069
51070      _this.sourceUpdater_.on('ready', function () {
51071        if (_this.hasEnoughInfoToAppend_()) {
51072          _this.processCallQueue_();
51073        }
51074      }); // Only the main loader needs to listen for pending timeline changes, as the main
51075      // loader should wait for audio to be ready to change its timeline so that both main
51076      // and audio timelines change together. For more details, see the
51077      // shouldWaitForTimelineChange function.
51078
51079
51080      if (_this.loaderType_ === 'main') {
51081        _this.timelineChangeController_.on('pendingtimelinechange', function () {
51082          if (_this.hasEnoughInfoToAppend_()) {
51083            _this.processCallQueue_();
51084          }
51085        });
51086      } // The main loader only listens on pending timeline changes, but the audio loader,
51087      // since its loads follow main, needs to listen on timeline changes. For more details,
51088      // see the shouldWaitForTimelineChange function.
51089
51090
51091      if (_this.loaderType_ === 'audio') {
51092        _this.timelineChangeController_.on('timelinechange', function () {
51093          if (_this.hasEnoughInfoToLoad_()) {
51094            _this.processLoadQueue_();
51095          }
51096
51097          if (_this.hasEnoughInfoToAppend_()) {
51098            _this.processCallQueue_();
51099          }
51100        });
51101      }
51102
51103      return _this;
51104    }
51105
51106    var _proto = SegmentLoader.prototype;
51107
51108    _proto.createTransmuxer_ = function createTransmuxer_() {
51109      var transmuxer = new TransmuxWorker();
51110      transmuxer.postMessage({
51111        action: 'init',
51112        options: {
51113          remux: false,
51114          alignGopsAtEnd: this.safeAppend_,
51115          keepOriginalTimestamps: true,
51116          handlePartialData: this.handlePartialData_
51117        }
51118      });
51119      return transmuxer;
51120    }
51121    /**
51122     * reset all of our media stats
51123     *
51124     * @private
51125     */
51126    ;
51127
51128    _proto.resetStats_ = function resetStats_() {
51129      this.mediaBytesTransferred = 0;
51130      this.mediaRequests = 0;
51131      this.mediaRequestsAborted = 0;
51132      this.mediaRequestsTimedout = 0;
51133      this.mediaRequestsErrored = 0;
51134      this.mediaTransferDuration = 0;
51135      this.mediaSecondsLoaded = 0;
51136    }
51137    /**
51138     * dispose of the SegmentLoader and reset to the default state
51139     */
51140    ;
51141
51142    _proto.dispose = function dispose() {
51143      this.trigger('dispose');
51144      this.state = 'DISPOSED';
51145      this.pause();
51146      this.abort_();
51147
51148      if (this.transmuxer_) {
51149        this.transmuxer_.terminate(); // Although it isn't an instance of a class, the segment transmuxer must still be
51150        // cleaned up.
51151
51152        segmentTransmuxer.dispose();
51153      }
51154
51155      this.resetStats_();
51156
51157      if (this.checkBufferTimeout_) {
51158        window$1.clearTimeout(this.checkBufferTimeout_);
51159      }
51160
51161      if (this.syncController_ && this.triggerSyncInfoUpdate_) {
51162        this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
51163      }
51164
51165      this.off();
51166    };
51167
51168    _proto.setAudio = function setAudio(enable) {
51169      this.audioDisabled_ = !enable;
51170
51171      if (enable) {
51172        this.appendInitSegment_.audio = true;
51173      } else {
51174        // remove current track audio if it gets disabled
51175        this.sourceUpdater_.removeAudio(0, this.duration_());
51176      }
51177    }
51178    /**
51179     * abort anything that is currently doing on with the SegmentLoader
51180     * and reset to a default state
51181     */
51182    ;
51183
51184    _proto.abort = function abort() {
51185      if (this.state !== 'WAITING') {
51186        if (this.pendingSegment_) {
51187          this.pendingSegment_ = null;
51188        }
51189
51190        return;
51191      }
51192
51193      this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
51194      // since we are no longer "waiting" on any requests. XHR callback is not always run
51195      // when the request is aborted. This will prevent the loader from being stuck in the
51196      // WAITING state indefinitely.
51197
51198      this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
51199      // next segment
51200
51201      if (!this.paused()) {
51202        this.monitorBuffer_();
51203      }
51204    }
51205    /**
51206     * abort all pending xhr requests and null any pending segements
51207     *
51208     * @private
51209     */
51210    ;
51211
51212    _proto.abort_ = function abort_() {
51213      if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
51214        this.pendingSegment_.abortRequests();
51215      } // clear out the segment being processed
51216
51217
51218      this.pendingSegment_ = null;
51219      this.callQueue_ = [];
51220      this.loadQueue_ = [];
51221      this.metadataQueue_.id3 = [];
51222      this.metadataQueue_.caption = [];
51223      this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
51224    };
51225
51226    _proto.checkForAbort_ = function checkForAbort_(requestId) {
51227      // If the state is APPENDING, then aborts will not modify the state, meaning the first
51228      // callback that happens should reset the state to READY so that loading can continue.
51229      if (this.state === 'APPENDING' && !this.pendingSegment_) {
51230        this.state = 'READY';
51231        return true;
51232      }
51233
51234      if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
51235        return true;
51236      }
51237
51238      return false;
51239    }
51240    /**
51241     * set an error on the segment loader and null out any pending segements
51242     *
51243     * @param {Error} error the error to set on the SegmentLoader
51244     * @return {Error} the error that was set or that is currently set
51245     */
51246    ;
51247
51248    _proto.error = function error(_error) {
51249      if (typeof _error !== 'undefined') {
51250        this.logger_('error occurred:', _error);
51251        this.error_ = _error;
51252      }
51253
51254      this.pendingSegment_ = null;
51255      return this.error_;
51256    };
51257
51258    _proto.endOfStream = function endOfStream() {
51259      this.ended_ = true;
51260
51261      if (this.transmuxer_) {
51262        // need to clear out any cached data to prepare for the new segment
51263        segmentTransmuxer.reset(this.transmuxer_);
51264      }
51265
51266      this.gopBuffer_.length = 0;
51267      this.pause();
51268      this.trigger('ended');
51269    }
51270    /**
51271     * Indicates which time ranges are buffered
51272     *
51273     * @return {TimeRange}
51274     *         TimeRange object representing the current buffered ranges
51275     */
51276    ;
51277
51278    _proto.buffered_ = function buffered_() {
51279      if (!this.sourceUpdater_ || !this.startingMediaInfo_) {
51280        return videojs$1.createTimeRanges();
51281      }
51282
51283      if (this.loaderType_ === 'main') {
51284        var _this$startingMediaIn = this.startingMediaInfo_,
51285            hasAudio = _this$startingMediaIn.hasAudio,
51286            hasVideo = _this$startingMediaIn.hasVideo,
51287            isMuxed = _this$startingMediaIn.isMuxed;
51288
51289        if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
51290          return this.sourceUpdater_.buffered();
51291        }
51292
51293        if (hasVideo) {
51294          return this.sourceUpdater_.videoBuffered();
51295        }
51296      } // One case that can be ignored for now is audio only with alt audio,
51297      // as we don't yet have proper support for that.
51298
51299
51300      return this.sourceUpdater_.audioBuffered();
51301    }
51302    /**
51303     * Gets and sets init segment for the provided map
51304     *
51305     * @param {Object} map
51306     *        The map object representing the init segment to get or set
51307     * @param {boolean=} set
51308     *        If true, the init segment for the provided map should be saved
51309     * @return {Object}
51310     *         map object for desired init segment
51311     */
51312    ;
51313
51314    _proto.initSegmentForMap = function initSegmentForMap(map, set) {
51315      if (set === void 0) {
51316        set = false;
51317      }
51318
51319      if (!map) {
51320        return null;
51321      }
51322
51323      var id = initSegmentId(map);
51324      var storedMap = this.initSegments_[id];
51325
51326      if (set && !storedMap && map.bytes) {
51327        this.initSegments_[id] = storedMap = {
51328          resolvedUri: map.resolvedUri,
51329          byterange: map.byterange,
51330          bytes: map.bytes,
51331          tracks: map.tracks,
51332          timescales: map.timescales
51333        };
51334      }
51335
51336      return storedMap || map;
51337    }
51338    /**
51339     * Gets and sets key for the provided key
51340     *
51341     * @param {Object} key
51342     *        The key object representing the key to get or set
51343     * @param {boolean=} set
51344     *        If true, the key for the provided key should be saved
51345     * @return {Object}
51346     *         Key object for desired key
51347     */
51348    ;
51349
51350    _proto.segmentKey = function segmentKey(key, set) {
51351      if (set === void 0) {
51352        set = false;
51353      }
51354
51355      if (!key) {
51356        return null;
51357      }
51358
51359      var id = segmentKeyId(key);
51360      var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
51361      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
51362
51363      if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
51364        this.keyCache_[id] = storedKey = {
51365          resolvedUri: key.resolvedUri,
51366          bytes: key.bytes
51367        };
51368      }
51369
51370      var result = {
51371        resolvedUri: (storedKey || key).resolvedUri
51372      };
51373
51374      if (storedKey) {
51375        result.bytes = storedKey.bytes;
51376      }
51377
51378      return result;
51379    }
51380    /**
51381     * Returns true if all configuration required for loading is present, otherwise false.
51382     *
51383     * @return {boolean} True if the all configuration is ready for loading
51384     * @private
51385     */
51386    ;
51387
51388    _proto.couldBeginLoading_ = function couldBeginLoading_() {
51389      return this.playlist_ && !this.paused();
51390    }
51391    /**
51392     * load a playlist and start to fill the buffer
51393     */
51394    ;
51395
51396    _proto.load = function load() {
51397      // un-pause
51398      this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
51399      // specified
51400
51401      if (!this.playlist_) {
51402        return;
51403      } // not sure if this is the best place for this
51404
51405
51406      this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
51407
51408      if (this.state === 'INIT' && this.couldBeginLoading_()) {
51409        return this.init_();
51410      } // if we're in the middle of processing a segment already, don't
51411      // kick off an additional segment request
51412
51413
51414      if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
51415        return;
51416      }
51417
51418      this.state = 'READY';
51419    }
51420    /**
51421     * Once all the starting parameters have been specified, begin
51422     * operation. This method should only be invoked from the INIT
51423     * state.
51424     *
51425     * @private
51426     */
51427    ;
51428
51429    _proto.init_ = function init_() {
51430      this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
51431      // audio data from the muxed content should be removed
51432
51433      this.resetEverything();
51434      return this.monitorBuffer_();
51435    }
51436    /**
51437     * set a playlist on the segment loader
51438     *
51439     * @param {PlaylistLoader} media the playlist to set on the segment loader
51440     */
51441    ;
51442
51443    _proto.playlist = function playlist(newPlaylist, options) {
51444      if (options === void 0) {
51445        options = {};
51446      }
51447
51448      if (!newPlaylist) {
51449        return;
51450      }
51451
51452      var oldPlaylist = this.playlist_;
51453      var segmentInfo = this.pendingSegment_;
51454      this.playlist_ = newPlaylist;
51455      this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
51456      // is always our zero-time so force a sync update each time the playlist
51457      // is refreshed from the server
51458      //
51459      // Use the INIT state to determine if playback has started, as the playlist sync info
51460      // should be fixed once requests begin (as sync points are generated based on sync
51461      // info), but not before then.
51462
51463      if (this.state === 'INIT') {
51464        newPlaylist.syncInfo = {
51465          mediaSequence: newPlaylist.mediaSequence,
51466          time: 0
51467        };
51468      }
51469
51470      var oldId = null;
51471
51472      if (oldPlaylist) {
51473        if (oldPlaylist.id) {
51474          oldId = oldPlaylist.id;
51475        } else if (oldPlaylist.uri) {
51476          oldId = oldPlaylist.uri;
51477        }
51478      }
51479
51480      this.logger_("playlist update [" + oldId + " => " + (newPlaylist.id || newPlaylist.uri) + "]"); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
51481      // in LIVE, we always want to update with new playlists (including refreshes)
51482
51483      this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
51484      // buffering now
51485
51486      if (this.state === 'INIT' && this.couldBeginLoading_()) {
51487        return this.init_();
51488      }
51489
51490      if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
51491        if (this.mediaIndex !== null || this.handlePartialData_) {
51492          // we must "resync" the segment loader when we switch renditions and
51493          // the segment loader is already synced to the previous rendition
51494          //
51495          // or if we're handling partial data, we need to ensure the transmuxer is cleared
51496          // out before we start adding more data
51497          this.resyncLoader();
51498        }
51499
51500        this.currentMediaInfo_ = void 0;
51501        this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
51502
51503        return;
51504      } // we reloaded the same playlist so we are in a live scenario
51505      // and we will likely need to adjust the mediaIndex
51506
51507
51508      var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
51509      this.logger_("live window shift [" + mediaSequenceDiff + "]"); // update the mediaIndex on the SegmentLoader
51510      // this is important because we can abort a request and this value must be
51511      // equal to the last appended mediaIndex
51512
51513      if (this.mediaIndex !== null) {
51514        this.mediaIndex -= mediaSequenceDiff;
51515      } // update the mediaIndex on the SegmentInfo object
51516      // this is important because we will update this.mediaIndex with this value
51517      // in `handleAppendsDone_` after the segment has been successfully appended
51518
51519
51520      if (segmentInfo) {
51521        segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
51522        // saved for the new playlist's segment, however, if the segment fell off the
51523        // playlist, we can leave the old reference and just lose the timing info
51524
51525        if (segmentInfo.mediaIndex >= 0) {
51526          segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
51527        }
51528      }
51529
51530      this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
51531    }
51532    /**
51533     * Prevent the loader from fetching additional segments. If there
51534     * is a segment request outstanding, it will finish processing
51535     * before the loader halts. A segment loader can be unpaused by
51536     * calling load().
51537     */
51538    ;
51539
51540    _proto.pause = function pause() {
51541      if (this.checkBufferTimeout_) {
51542        window$1.clearTimeout(this.checkBufferTimeout_);
51543        this.checkBufferTimeout_ = null;
51544      }
51545    }
51546    /**
51547     * Returns whether the segment loader is fetching additional
51548     * segments when given the opportunity. This property can be
51549     * modified through calls to pause() and load().
51550     */
51551    ;
51552
51553    _proto.paused = function paused() {
51554      return this.checkBufferTimeout_ === null;
51555    }
51556    /**
51557     * Delete all the buffered data and reset the SegmentLoader
51558     *
51559     * @param {Function} [done] an optional callback to be executed when the remove
51560     * operation is complete
51561     */
51562    ;
51563
51564    _proto.resetEverything = function resetEverything(done) {
51565      this.ended_ = false;
51566      this.appendInitSegment_ = {
51567        audio: true,
51568        video: true
51569      };
51570      this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
51571      // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
51572      // we then clamp the value to duration if necessary.
51573
51574      this.remove(0, Infinity, done); // clears fmp4 captions
51575
51576      if (this.transmuxer_) {
51577        this.transmuxer_.postMessage({
51578          action: 'clearAllMp4Captions'
51579        });
51580      }
51581    }
51582    /**
51583     * Force the SegmentLoader to resync and start loading around the currentTime instead
51584     * of starting at the end of the buffer
51585     *
51586     * Useful for fast quality changes
51587     */
51588    ;
51589
51590    _proto.resetLoader = function resetLoader() {
51591      this.fetchAtBuffer_ = false;
51592      this.resyncLoader();
51593    }
51594    /**
51595     * Force the SegmentLoader to restart synchronization and make a conservative guess
51596     * before returning to the simple walk-forward method
51597     */
51598    ;
51599
51600    _proto.resyncLoader = function resyncLoader() {
51601      if (this.transmuxer_) {
51602        // need to clear out any cached data to prepare for the new segment
51603        segmentTransmuxer.reset(this.transmuxer_);
51604      }
51605
51606      this.mediaIndex = null;
51607      this.syncPoint_ = null;
51608      this.isPendingTimestampOffset_ = false;
51609      this.callQueue_ = [];
51610      this.loadQueue_ = [];
51611      this.metadataQueue_.id3 = [];
51612      this.metadataQueue_.caption = [];
51613      this.abort();
51614
51615      if (this.transmuxer_) {
51616        this.transmuxer_.postMessage({
51617          action: 'clearParsedMp4Captions'
51618        });
51619      }
51620    }
51621    /**
51622     * Remove any data in the source buffer between start and end times
51623     *
51624     * @param {number} start - the start time of the region to remove from the buffer
51625     * @param {number} end - the end time of the region to remove from the buffer
51626     * @param {Function} [done] - an optional callback to be executed when the remove
51627     * operation is complete
51628     */
51629    ;
51630
51631    _proto.remove = function remove(start, end, done) {
51632      if (done === void 0) {
51633        done = function done() {};
51634      } // clamp end to duration if we need to remove everything.
51635      // This is due to a browser bug that causes issues if we remove to Infinity.
51636      // videojs/videojs-contrib-hls#1225
51637
51638
51639      if (end === Infinity) {
51640        end = this.duration_();
51641      }
51642
51643      if (!this.sourceUpdater_ || !this.currentMediaInfo_) {
51644        // nothing to remove if we haven't processed any media
51645        return;
51646      } // set it to one to complete this function's removes
51647
51648
51649      var removesRemaining = 1;
51650
51651      var removeFinished = function removeFinished() {
51652        removesRemaining--;
51653
51654        if (removesRemaining === 0) {
51655          done();
51656        }
51657      };
51658
51659      if (!this.audioDisabled_) {
51660        removesRemaining++;
51661        this.sourceUpdater_.removeAudio(start, end, removeFinished);
51662      }
51663
51664      if (this.loaderType_ === 'main' && this.currentMediaInfo_ && this.currentMediaInfo_.hasVideo) {
51665        this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
51666        removesRemaining++;
51667        this.sourceUpdater_.removeVideo(start, end, removeFinished);
51668      } // remove any captions and ID3 tags
51669
51670
51671      for (var track in this.inbandTextTracks_) {
51672        removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
51673      }
51674
51675      removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
51676
51677      removeFinished();
51678    }
51679    /**
51680     * (re-)schedule monitorBufferTick_ to run as soon as possible
51681     *
51682     * @private
51683     */
51684    ;
51685
51686    _proto.monitorBuffer_ = function monitorBuffer_() {
51687      if (this.checkBufferTimeout_) {
51688        window$1.clearTimeout(this.checkBufferTimeout_);
51689      }
51690
51691      this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
51692    }
51693    /**
51694     * As long as the SegmentLoader is in the READY state, periodically
51695     * invoke fillBuffer_().
51696     *
51697     * @private
51698     */
51699    ;
51700
51701    _proto.monitorBufferTick_ = function monitorBufferTick_() {
51702      if (this.state === 'READY') {
51703        this.fillBuffer_();
51704      }
51705
51706      if (this.checkBufferTimeout_) {
51707        window$1.clearTimeout(this.checkBufferTimeout_);
51708      }
51709
51710      this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
51711    }
51712    /**
51713     * fill the buffer with segements unless the sourceBuffers are
51714     * currently updating
51715     *
51716     * Note: this function should only ever be called by monitorBuffer_
51717     * and never directly
51718     *
51719     * @private
51720     */
51721    ;
51722
51723    _proto.fillBuffer_ = function fillBuffer_() {
51724      // TODO since the source buffer maintains a queue, and we shouldn't call this function
51725      // except when we're ready for the next segment, this check can most likely be removed
51726      if (this.sourceUpdater_.updating()) {
51727        return;
51728      }
51729
51730      if (!this.syncPoint_) {
51731        this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
51732      }
51733
51734      var buffered = this.buffered_(); // see if we need to begin loading immediately
51735
51736      var segmentInfo = this.checkBuffer_(buffered, this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
51737
51738      if (!segmentInfo) {
51739        return;
51740      }
51741
51742      segmentInfo.timestampOffset = timestampOffsetForSegment({
51743        segmentTimeline: segmentInfo.timeline,
51744        currentTimeline: this.currentTimeline_,
51745        startOfSegment: segmentInfo.startOfSegment,
51746        buffered: buffered,
51747        overrideCheck: this.isPendingTimestampOffset_
51748      });
51749      this.isPendingTimestampOffset_ = false;
51750
51751      if (typeof segmentInfo.timestampOffset === 'number') {
51752        this.timelineChangeController_.pendingTimelineChange({
51753          type: this.loaderType_,
51754          from: this.currentTimeline_,
51755          to: segmentInfo.timeline
51756        });
51757      }
51758
51759      this.loadSegment_(segmentInfo);
51760    }
51761    /**
51762     * Determines if we should call endOfStream on the media source based
51763     * on the state of the buffer or if appened segment was the final
51764     * segment in the playlist.
51765     *
51766     * @param {number} [mediaIndex] the media index of segment we last appended
51767     * @param {Object} [playlist] a media playlist object
51768     * @return {boolean} do we need to call endOfStream on the MediaSource
51769     */
51770    ;
51771
51772    _proto.isEndOfStream_ = function isEndOfStream_(mediaIndex, playlist) {
51773      if (mediaIndex === void 0) {
51774        mediaIndex = this.mediaIndex;
51775      }
51776
51777      if (playlist === void 0) {
51778        playlist = this.playlist_;
51779      }
51780
51781      if (!playlist || !this.mediaSource_) {
51782        return false;
51783      } // mediaIndex is zero based but length is 1 based
51784
51785
51786      var appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // if we've buffered to the end of the video, we need to call endOfStream
51787      // so that MediaSources can trigger the `ended` event when it runs out of
51788      // buffered data instead of waiting for me
51789
51790      return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment;
51791    }
51792    /**
51793     * Determines what segment request should be made, given current playback
51794     * state.
51795     *
51796     * @param {TimeRanges} buffered - the state of the buffer
51797     * @param {Object} playlist - the playlist object to fetch segments from
51798     * @param {number} mediaIndex - the previous mediaIndex fetched or null
51799     * @param {boolean} hasPlayed - a flag indicating whether we have played or not
51800     * @param {number} currentTime - the playback position in seconds
51801     * @param {Object} syncPoint - a segment info object that describes the
51802     * @return {Object} a segment request object that describes the segment to load
51803     */
51804    ;
51805
51806    _proto.checkBuffer_ = function checkBuffer_(buffered, playlist, currentMediaIndex, hasPlayed, currentTime, syncPoint) {
51807      var lastBufferedEnd = 0;
51808
51809      if (buffered.length) {
51810        lastBufferedEnd = buffered.end(buffered.length - 1);
51811      }
51812
51813      var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
51814
51815      if (!playlist.segments.length) {
51816        return null;
51817      } // if there is plenty of content buffered, and the video has
51818      // been played before relax for awhile
51819
51820
51821      if (bufferedTime >= this.goalBufferLength_()) {
51822        return null;
51823      } // if the video has not yet played once, and we already have
51824      // one segment downloaded do nothing
51825
51826
51827      if (!hasPlayed && bufferedTime >= 1) {
51828        return null;
51829      }
51830
51831      var nextMediaIndex = null;
51832      var startOfSegment;
51833      var isSyncRequest = false; // When the syncPoint is null, there is no way of determining a good
51834      // conservative segment index to fetch from
51835      // The best thing to do here is to get the kind of sync-point data by
51836      // making a request
51837
51838      if (syncPoint === null) {
51839        nextMediaIndex = this.getSyncSegmentCandidate_(playlist);
51840        isSyncRequest = true;
51841      } else if (currentMediaIndex !== null) {
51842        // Under normal playback conditions fetching is a simple walk forward
51843        var segment = playlist.segments[currentMediaIndex];
51844
51845        if (segment && segment.end) {
51846          startOfSegment = segment.end;
51847        } else {
51848          startOfSegment = lastBufferedEnd;
51849        }
51850
51851        nextMediaIndex = currentMediaIndex + 1; // There is a sync-point but the lack of a mediaIndex indicates that
51852        // we need to make a good conservative guess about which segment to
51853        // fetch
51854      } else if (this.fetchAtBuffer_) {
51855        // Find the segment containing the end of the buffer
51856        var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
51857        nextMediaIndex = mediaSourceInfo.mediaIndex;
51858        startOfSegment = mediaSourceInfo.startTime;
51859      } else {
51860        // Find the segment containing currentTime
51861        var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
51862
51863        nextMediaIndex = _mediaSourceInfo.mediaIndex;
51864        startOfSegment = _mediaSourceInfo.startTime;
51865      }
51866
51867      var segmentInfo = this.generateSegmentInfo_(playlist, nextMediaIndex, startOfSegment, isSyncRequest);
51868
51869      if (!segmentInfo) {
51870        return;
51871      } // if this is the last segment in the playlist
51872      // we are not seeking and end of stream has already been called
51873      // do not re-request
51874
51875
51876      if (this.mediaSource_ && this.playlist_ && segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
51877        return;
51878      }
51879
51880      this.logger_("checkBuffer_ returning " + segmentInfo.uri, {
51881        segmentInfo: segmentInfo,
51882        playlist: playlist,
51883        currentMediaIndex: currentMediaIndex,
51884        nextMediaIndex: nextMediaIndex,
51885        startOfSegment: startOfSegment,
51886        isSyncRequest: isSyncRequest
51887      });
51888      return segmentInfo;
51889    }
51890    /**
51891     * The segment loader has no recourse except to fetch a segment in the
51892     * current playlist and use the internal timestamps in that segment to
51893     * generate a syncPoint. This function returns a good candidate index
51894     * for that process.
51895     *
51896     * @param {Object} playlist - the playlist object to look for a
51897     * @return {number} An index of a segment from the playlist to load
51898     */
51899    ;
51900
51901    _proto.getSyncSegmentCandidate_ = function getSyncSegmentCandidate_(playlist) {
51902      var _this2 = this;
51903
51904      if (this.currentTimeline_ === -1) {
51905        return 0;
51906      }
51907
51908      var segmentIndexArray = playlist.segments.map(function (s, i) {
51909        return {
51910          timeline: s.timeline,
51911          segmentIndex: i
51912        };
51913      }).filter(function (s) {
51914        return s.timeline === _this2.currentTimeline_;
51915      });
51916
51917      if (segmentIndexArray.length) {
51918        return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
51919      }
51920
51921      return Math.max(playlist.segments.length - 1, 0);
51922    };
51923
51924    _proto.generateSegmentInfo_ = function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
51925      if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
51926        return null;
51927      }
51928
51929      var segment = playlist.segments[mediaIndex];
51930      var audioBuffered = this.sourceUpdater_.audioBuffered();
51931      var videoBuffered = this.sourceUpdater_.videoBuffered();
51932      var audioAppendStart;
51933      var gopsToAlignWith;
51934
51935      if (audioBuffered.length) {
51936        // since the transmuxer is using the actual timing values, but the buffer is
51937        // adjusted by the timestamp offset, we must adjust the value here
51938        audioAppendStart = audioBuffered.end(audioBuffered.length - 1) - this.sourceUpdater_.audioTimestampOffset();
51939      }
51940
51941      if (videoBuffered.length) {
51942        gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_, // since the transmuxer is using the actual timing values, but the time is
51943        // adjusted by the timestmap offset, we must adjust the value here
51944        this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
51945      }
51946
51947      return {
51948        requestId: 'segment-loader-' + Math.random(),
51949        // resolve the segment URL relative to the playlist
51950        uri: segment.resolvedUri,
51951        // the segment's mediaIndex at the time it was requested
51952        mediaIndex: mediaIndex,
51953        // whether or not to update the SegmentLoader's state with this
51954        // segment's mediaIndex
51955        isSyncRequest: isSyncRequest,
51956        startOfSegment: startOfSegment,
51957        // the segment's playlist
51958        playlist: playlist,
51959        // unencrypted bytes of the segment
51960        bytes: null,
51961        // when a key is defined for this segment, the encrypted bytes
51962        encryptedBytes: null,
51963        // The target timestampOffset for this segment when we append it
51964        // to the source buffer
51965        timestampOffset: null,
51966        // The timeline that the segment is in
51967        timeline: segment.timeline,
51968        // The expected duration of the segment in seconds
51969        duration: segment.duration,
51970        // retain the segment in case the playlist updates while doing an async process
51971        segment: segment,
51972        byteLength: 0,
51973        transmuxer: this.transmuxer_,
51974        audioAppendStart: audioAppendStart,
51975        gopsToAlignWith: gopsToAlignWith
51976      };
51977    }
51978    /**
51979     * Determines if the network has enough bandwidth to complete the current segment
51980     * request in a timely manner. If not, the request will be aborted early and bandwidth
51981     * updated to trigger a playlist switch.
51982     *
51983     * @param {Object} stats
51984     *        Object containing stats about the request timing and size
51985     * @return {boolean} True if the request was aborted, false otherwise
51986     * @private
51987     */
51988    ;
51989
51990    _proto.abortRequestEarly_ = function abortRequestEarly_(stats) {
51991      if (this.vhs_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
51992      // TODO: Replace using timeout with a boolean indicating whether this playlist is
51993      //       the lowestEnabledRendition.
51994      !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
51995      !this.playlist_.attributes.BANDWIDTH) {
51996        return false;
51997      } // Wait at least 1 second since the first byte of data has been received before
51998      // using the calculated bandwidth from the progress event to allow the bitrate
51999      // to stabilize
52000
52001
52002      if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
52003        return false;
52004      }
52005
52006      var currentTime = this.currentTime_();
52007      var measuredBandwidth = stats.bandwidth;
52008      var segmentDuration = this.pendingSegment_.duration;
52009      var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
52010      // if we are only left with less than 1 second when the request completes.
52011      // A negative timeUntilRebuffering indicates we are already rebuffering
52012
52013      var timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
52014      // is larger than the estimated time until the player runs out of forward buffer
52015
52016      if (requestTimeRemaining <= timeUntilRebuffer$1) {
52017        return false;
vendor: 20,105 bytes, lines 52018-52493
52018      }
52019
52020      var switchCandidate = minRebufferMaxBandwidthSelector({
52021        master: this.vhs_.playlists.master,
52022        currentTime: currentTime,
52023        bandwidth: measuredBandwidth,
52024        duration: this.duration_(),
52025        segmentDuration: segmentDuration,
52026        timeUntilRebuffer: timeUntilRebuffer$1,
52027        currentTimeline: this.currentTimeline_,
52028        syncController: this.syncController_
52029      });
52030
52031      if (!switchCandidate) {
52032        return;
52033      }
52034
52035      var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
52036      var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
52037      var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
52038      // potential round trip time of the new request so that we are not too aggressive
52039      // with switching to a playlist that might save us a fraction of a second.
52040
52041      if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
52042        minimumTimeSaving = 1;
52043      }
52044
52045      if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
52046        return false;
52047      } // set the bandwidth to that of the desired playlist being sure to scale by
52048      // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
52049      // don't trigger a bandwidthupdate as the bandwidth is artifial
52050
52051
52052      this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
52053      this.abort();
52054      this.trigger('earlyabort');
52055      return true;
52056    };
52057
52058    _proto.handleAbort_ = function handleAbort_() {
52059      this.mediaRequestsAborted += 1;
52060    }
52061    /**
52062     * XHR `progress` event handler
52063     *
52064     * @param {Event}
52065     *        The XHR `progress` event
52066     * @param {Object} simpleSegment
52067     *        A simplified segment object copy
52068     * @private
52069     */
52070    ;
52071
52072    _proto.handleProgress_ = function handleProgress_(event, simpleSegment) {
52073      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52074        return;
52075      }
52076
52077      this.trigger('progress');
52078    };
52079
52080    _proto.handleTrackInfo_ = function handleTrackInfo_(simpleSegment, trackInfo) {
52081      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52082        return;
52083      }
52084
52085      if (this.checkForIllegalMediaSwitch(trackInfo)) {
52086        return;
52087      }
52088
52089      trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
52090      // Guard against cases where we're not getting track info at all until we are
52091      // certain that all streams will provide it.
52092
52093      if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
52094        this.appendInitSegment_ = {
52095          audio: true,
52096          video: true
52097        };
52098        this.startingMediaInfo_ = trackInfo;
52099        this.currentMediaInfo_ = trackInfo;
52100        this.logger_('trackinfo update', trackInfo);
52101        this.trigger('trackinfo');
52102      } // trackinfo may cause an abort if the trackinfo
52103      // causes a codec change to an unsupported codec.
52104
52105
52106      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52107        return;
52108      } // set trackinfo on the pending segment so that
52109      // it can append.
52110
52111
52112      this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
52113
52114      if (this.hasEnoughInfoToAppend_()) {
52115        this.processCallQueue_();
52116      }
52117    };
52118
52119    _proto.handleTimingInfo_ = function handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
52120      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52121        return;
52122      }
52123
52124      var segmentInfo = this.pendingSegment_;
52125      var timingInfoProperty = timingInfoPropertyForMedia(mediaType);
52126      segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
52127      segmentInfo[timingInfoProperty][timeType] = time;
52128      this.logger_("timinginfo: " + mediaType + " - " + timeType + " - " + time); // check if any calls were waiting on the timing info
52129
52130      if (this.hasEnoughInfoToAppend_()) {
52131        this.processCallQueue_();
52132      }
52133    };
52134
52135    _proto.handleCaptions_ = function handleCaptions_(simpleSegment, captionData) {
52136      var _this3 = this;
52137
52138      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52139        return;
52140      } // This could only happen with fmp4 segments, but
52141      // should still not happen in general
52142
52143
52144      if (captionData.length === 0) {
52145        this.logger_('SegmentLoader received no captions from a caption event');
52146        return;
52147      }
52148
52149      var segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
52150      // can be adjusted by the timestamp offset
52151
52152      if (!segmentInfo.hasAppendedData_) {
52153        this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
52154        return;
52155      }
52156
52157      var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
52158      var captionTracks = {}; // get total start/end and captions for each track/stream
52159
52160      captionData.forEach(function (caption) {
52161        // caption.stream is actually a track name...
52162        // set to the existing values in tracks or default values
52163        captionTracks[caption.stream] = captionTracks[caption.stream] || {
52164          // Infinity, as any other value will be less than this
52165          startTime: Infinity,
52166          captions: [],
52167          // 0 as an other value will be more than this
52168          endTime: 0
52169        };
52170        var captionTrack = captionTracks[caption.stream];
52171        captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
52172        captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
52173        captionTrack.captions.push(caption);
52174      });
52175      Object.keys(captionTracks).forEach(function (trackName) {
52176        var _captionTracks$trackN = captionTracks[trackName],
52177            startTime = _captionTracks$trackN.startTime,
52178            endTime = _captionTracks$trackN.endTime,
52179            captions = _captionTracks$trackN.captions;
52180        var inbandTextTracks = _this3.inbandTextTracks_;
52181
52182        _this3.logger_("adding cues from " + startTime + " -> " + endTime + " for " + trackName);
52183
52184        createCaptionsTrackIfNotExists(inbandTextTracks, _this3.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
52185        // We do this because a rendition change that also changes the timescale for captions
52186        // will result in captions being re-parsed for certain segments. If we add them again
52187        // without clearing we will have two of the same captions visible.
52188
52189        removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
52190        addCaptionData({
52191          captionArray: captions,
52192          inbandTextTracks: inbandTextTracks,
52193          timestampOffset: timestampOffset
52194        });
52195      }); // Reset stored captions since we added parsed
52196      // captions to a text track at this point
52197
52198      if (this.transmuxer_) {
52199        this.transmuxer_.postMessage({
52200          action: 'clearParsedMp4Captions'
52201        });
52202      }
52203    };
52204
52205    _proto.handleId3_ = function handleId3_(simpleSegment, id3Frames, dispatchType) {
52206      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52207        return;
52208      }
52209
52210      var segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
52211
52212      if (!segmentInfo.hasAppendedData_) {
52213        this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
52214        return;
52215      }
52216
52217      var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
52218      // audio/video source with a metadata track, and an alt audio with a metadata track.
52219      // However, this probably won't happen, and if it does it can be handled then.
52220
52221      createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_);
52222      addMetadata({
52223        inbandTextTracks: this.inbandTextTracks_,
52224        metadataArray: id3Frames,
52225        timestampOffset: timestampOffset,
52226        videoDuration: this.duration_()
52227      });
52228    };
52229
52230    _proto.processMetadataQueue_ = function processMetadataQueue_() {
52231      this.metadataQueue_.id3.forEach(function (fn) {
52232        return fn();
52233      });
52234      this.metadataQueue_.caption.forEach(function (fn) {
52235        return fn();
52236      });
52237      this.metadataQueue_.id3 = [];
52238      this.metadataQueue_.caption = [];
52239    };
52240
52241    _proto.processCallQueue_ = function processCallQueue_() {
52242      var callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
52243      // functions may check the length of the load queue and default to pushing themselves
52244      // back onto the queue.
52245
52246      this.callQueue_ = [];
52247      callQueue.forEach(function (fun) {
52248        return fun();
52249      });
52250    };
52251
52252    _proto.processLoadQueue_ = function processLoadQueue_() {
52253      var loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
52254      // functions may check the length of the load queue and default to pushing themselves
52255      // back onto the queue.
52256
52257      this.loadQueue_ = [];
52258      loadQueue.forEach(function (fun) {
52259        return fun();
52260      });
52261    }
52262    /**
52263     * Determines whether the loader has enough info to load the next segment.
52264     *
52265     * @return {boolean}
52266     *         Whether or not the loader has enough info to load the next segment
52267     */
52268    ;
52269
52270    _proto.hasEnoughInfoToLoad_ = function hasEnoughInfoToLoad_() {
52271      // Since primary timing goes by video, only the audio loader potentially needs to wait
52272      // to load.
52273      if (this.loaderType_ !== 'audio') {
52274        return true;
52275      }
52276
52277      var segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
52278      // enough info to load.
52279
52280      if (!segmentInfo) {
52281        return false;
52282      } // The first segment can and should be loaded immediately so that source buffers are
52283      // created together (before appending). Source buffer creation uses the presence of
52284      // audio and video data to determine whether to create audio/video source buffers, and
52285      // uses processed (transmuxed or parsed) media to determine the types required.
52286
52287
52288      if (!this.currentMediaInfo_) {
52289        return true;
52290      }
52291
52292      if ( // Technically, instead of waiting to load a segment on timeline changes, a segment
52293      // can be requested and downloaded and only wait before it is transmuxed or parsed.
52294      // But in practice, there are a few reasons why it is better to wait until a loader
52295      // is ready to append that segment before requesting and downloading:
52296      //
52297      // 1. Because audio and main loaders cross discontinuities together, if this loader
52298      //    is waiting for the other to catch up, then instead of requesting another
52299      //    segment and using up more bandwidth, by not yet loading, more bandwidth is
52300      //    allotted to the loader currently behind.
52301      // 2. media-segment-request doesn't have to have logic to consider whether a segment
52302      // is ready to be processed or not, isolating the queueing behavior to the loader.
52303      // 3. The audio loader bases some of its segment properties on timing information
52304      //    provided by the main loader, meaning that, if the logic for waiting on
52305      //    processing was in media-segment-request, then it would also need to know how
52306      //    to re-generate the segment information after the main loader caught up.
52307      shouldWaitForTimelineChange({
52308        timelineChangeController: this.timelineChangeController_,
52309        currentTimeline: this.currentTimeline_,
52310        segmentTimeline: segmentInfo.timeline,
52311        loaderType: this.loaderType_,
52312        audioDisabled: this.audioDisabled_
52313      })) {
52314        return false;
52315      }
52316
52317      return true;
52318    };
52319
52320    _proto.hasEnoughInfoToAppend_ = function hasEnoughInfoToAppend_() {
52321      if (!this.sourceUpdater_.ready()) {
52322        // waiting on one of the segment loaders to get enough data to create source buffers
52323        return false;
52324      }
52325
52326      var segmentInfo = this.pendingSegment_; // no segment to append any data for or
52327      // we do not have information on this specific
52328      // segment yet
52329
52330      if (!segmentInfo || !segmentInfo.trackInfo) {
52331        return false;
52332      }
52333
52334      if (!this.handlePartialData_) {
52335        var _this$currentMediaInf = this.currentMediaInfo_,
52336            hasAudio = _this$currentMediaInf.hasAudio,
52337            hasVideo = _this$currentMediaInf.hasVideo,
52338            isMuxed = _this$currentMediaInf.isMuxed;
52339
52340        if (hasVideo && !segmentInfo.videoTimingInfo) {
52341          return false;
52342        } // muxed content only relies on video timing information for now.
52343
52344
52345        if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
52346          return false;
52347        }
52348      }
52349
52350      if (shouldWaitForTimelineChange({
52351        timelineChangeController: this.timelineChangeController_,
52352        currentTimeline: this.currentTimeline_,
52353        segmentTimeline: segmentInfo.timeline,
52354        loaderType: this.loaderType_,
52355        audioDisabled: this.audioDisabled_
52356      })) {
52357        return false;
52358      }
52359
52360      return true;
52361    };
52362
52363    _proto.handleData_ = function handleData_(simpleSegment, result) {
52364      if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) {
52365        return;
52366      } // If there's anything in the call queue, then this data came later and should be
52367      // executed after the calls currently queued.
52368
52369
52370      if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
52371        this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
52372        return;
52373      }
52374
52375      var segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
52376
52377      this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
52378
52379      this.updateMediaSecondsLoaded_(segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
52380      // logic may change behavior depending on the state, and changing state too early may
52381      // inflate our estimates of bandwidth. In the future this should be re-examined to
52382      // note more granular states.
52383      // don't process and append data if the mediaSource is closed
52384
52385      if (this.mediaSource_.readyState === 'closed') {
52386        return;
52387      } // if this request included an initialization segment, save that data
52388      // to the initSegment cache
52389
52390
52391      if (simpleSegment.map) {
52392        simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
52393
52394        segmentInfo.segment.map = simpleSegment.map;
52395      } // if this request included a segment key, save that data in the cache
52396
52397
52398      if (simpleSegment.key) {
52399        this.segmentKey(simpleSegment.key, true);
52400      }
52401
52402      segmentInfo.isFmp4 = simpleSegment.isFmp4;
52403      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
52404
52405      if (segmentInfo.isFmp4) {
52406        this.trigger('fmp4');
52407        segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
52408      } else {
52409        var useVideoTimingInfo = this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo;
52410        var firstVideoFrameTimeForData;
52411
52412        if (useVideoTimingInfo) {
52413          firstVideoFrameTimeForData = this.handlePartialData_ ? result.videoFramePtsTime : segmentInfo.videoTimingInfo.start;
52414        } // Segment loader knows more about segment timing than the transmuxer (in certain
52415        // aspects), so make any changes required for a more accurate start time.
52416        // Don't set the end time yet, as the segment may not be finished processing.
52417
52418
52419        segmentInfo.timingInfo.start = this.trueSegmentStart_({
52420          currentStart: segmentInfo.timingInfo.start,
52421          playlist: segmentInfo.playlist,
52422          mediaIndex: segmentInfo.mediaIndex,
52423          currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
52424          useVideoTimingInfo: useVideoTimingInfo,
52425          firstVideoFrameTimeForData: firstVideoFrameTimeForData,
52426          videoTimingInfo: segmentInfo.videoTimingInfo,
52427          audioTimingInfo: segmentInfo.audioTimingInfo
52428        });
52429      } // Init segments for audio and video only need to be appended in certain cases. Now
52430      // that data is about to be appended, we can check the final cases to determine
52431      // whether we should append an init segment.
52432
52433
52434      this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
52435      // as we use the start of the segment to offset the best guess (playlist provided)
52436      // timestamp offset.
52437
52438      this.updateSourceBufferTimestampOffset_(segmentInfo); // Save some state so that in the future anything waiting on first append (and/or
52439      // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
52440      // we need some notion of whether the timestamp offset or other relevant information
52441      // has had a chance to be set.
52442
52443      segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
52444
52445      this.processMetadataQueue_();
52446      this.appendData_(segmentInfo, result);
52447    };
52448
52449    _proto.updateAppendInitSegmentStatus = function updateAppendInitSegmentStatus(segmentInfo, type) {
52450      // alt audio doesn't manage timestamp offset
52451      if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' && // in the case that we're handling partial data, we don't want to append an init
52452      // segment for each chunk
52453      !segmentInfo.changedTimestampOffset) {
52454        // if the timestamp offset changed, the timeline may have changed, so we have to re-
52455        // append init segments
52456        this.appendInitSegment_ = {
52457          audio: true,
52458          video: true
52459        };
52460      }
52461
52462      if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
52463        // make sure we append init segment on playlist changes, in case the media config
52464        // changed
52465        this.appendInitSegment_[type] = true;
52466      }
52467    };
52468
52469    _proto.getInitSegmentAndUpdateState_ = function getInitSegmentAndUpdateState_(_ref3) {
52470      var type = _ref3.type,
52471          initSegment = _ref3.initSegment,
52472          map = _ref3.map,
52473          playlist = _ref3.playlist; // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
52474      // (Section 3) required to parse the applicable Media Segments.  It applies to every
52475      // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
52476      // or until the end of the playlist."
52477      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
52478
52479      if (map) {
52480        var id = initSegmentId(map);
52481
52482        if (this.activeInitSegmentId_ === id) {
52483          // don't need to re-append the init segment if the ID matches
52484          return null;
52485        } // a map-specified init segment takes priority over any transmuxed (or otherwise
52486        // obtained) init segment
52487        //
52488        // this also caches the init segment for later use
52489
52490
52491        initSegment = this.initSegmentForMap(map, true).bytes;
52492        this.activeInitSegmentId_ = id;
52493      }
52493 // We used to always prepend init segments for video, however, that shouldn't be
52494      // necessary. Instead, we should only append on changes, similar to what we've always
52495      // done for audio. This is more important (though may not be that important) for
52496      // frame-by-frame appending for LHLS, simply because of the increased quantity of
52497      // appends.
52498
52499
52500      if (initSegment && this.appendInitSegment_[type]) {
52501        // Make sure we track the playlist that we last used for the init segment, so that
52502        // we can re-append the init segment in the event that we get data from a new
52503        // playlist. Discontinuities and track changes are handled in other sections.
52504        this.playlistOfLastInitSegment_[type] = playlist; // we should only be appending the next init segment if we detect a change, or if
52505        // the segment has a map
52506
52507        this.appendInitSegment_[type] = map ? true : false; // we need to clear out the fmp4 active init segment id, since
52508        // we are appending the muxer init segment
52509
52510        this.activeInitSegmentId_ = null;
52511        return initSegment;
52512      }
52513
52514      return null;
52515    };
52516
52517    _proto.appendToSourceBuffer_ = function appendToSourceBuffer_(_ref4) {
52518      var _this4 = this;
52519
52520      var segmentInfo = _ref4.segmentInfo,
52521          type = _ref4.type,
52522          initSegment = _ref4.initSegment,
52523          data = _ref4.data;
52524      var segments = [data];
52525      var byteLength = data.byteLength;
52526
52527      if (initSegment) {
52528        // if the media initialization segment is changing, append it before the content
52529        // segment
52530        segments.unshift(initSegment);
52531        byteLength += initSegment.byteLength;
52532      } // Technically we should be OK appending the init segment separately, however, we
52533      // haven't yet tested that, and prepending is how we have always done things.
52534
52535
52536      var bytes = concatSegments({
52537        bytes: byteLength,
52538        segments: segments
52539      });
52540      this.sourceUpdater_.appendBuffer({
52541        segmentInfo: segmentInfo,
52542        type: type,
52543        bytes: bytes
52544      }, function (error) {
52545        if (error) {
52546          _this4.error(type + " append of " + bytes.length + "b failed for segment #" + segmentInfo.mediaIndex + " in playlist " + segmentInfo.playlist.id); // If an append errors, we can't recover.
52547          // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
52548          // Trigger a special error so that it can be handled separately from normal,
52549          // recoverable errors.
52550
52551
52552          _this4.trigger('appenderror');
52553        }
52554      });
52555    };
52556
52557    _proto.handleVideoSegmentTimingInfo_ = function handleVideoSegmentTimingInfo_(requestId, videoSegmentTimingInfo) {
52558      if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
52559        return;
52560      }
52561
52562      var segment = this.pendingSegment_.segment;
52563
52564      if (!segment.videoTimingInfo) {
52565        segment.videoTimingInfo = {};
52566      }
52567
52568      segment.videoTimingInfo.transmuxerPrependedSeconds = videoSegmentTimingInfo.prependedContentDuration || 0;
52569      segment.videoTimingInfo.transmuxedPresentationStart = videoSegmentTimingInfo.start.presentation;
52570      segment.videoTimingInfo.transmuxedPresentationEnd = videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
52571
52572      segment.videoTimingInfo.baseMediaDecodeTime = videoSegmentTimingInfo.baseMediaDecodeTime;
52573    };
52574
52575    _proto.appendData_ = function appendData_(segmentInfo, result) {
52576      var type = result.type,
52577          data = result.data;
52578
52579      if (!data || !data.byteLength) {
52580        return;
52581      }
52582
52583      if (type === 'audio' && this.audioDisabled_) {
52584        return;
52585      }
52586
52587      var initSegment = this.getInitSegmentAndUpdateState_({
52588        type: type,
52589        initSegment: result.initSegment,
52590        playlist: segmentInfo.playlist,
52591        map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
52592      });
52593      this.appendToSourceBuffer_({
52594        segmentInfo: segmentInfo,
52595        type: type,
52596        initSegment: initSegment,
52597        data: data
52598      });
52599    }
52600    /**
52601     * load a specific segment from a request into the buffer
52602     *
52603     * @private
52604     */
52605    ;
52606
52607    _proto.loadSegment_ = function loadSegment_(segmentInfo) {
52608      var _this5 = this;
52609
52610      this.state = 'WAITING';
52611      this.pendingSegment_ = segmentInfo;
52612      this.trimBackBuffer_(segmentInfo);
52613
52614      if (typeof segmentInfo.timestampOffset === 'number') {
52615        if (this.transmuxer_) {
52616          this.transmuxer_.postMessage({
52617            action: 'clearAllMp4Captions'
52618          });
52619        }
52620      }
52621
52622      if (!this.hasEnoughInfoToLoad_()) {
52623        this.loadQueue_.push(function () {
52624          var buffered = _this5.buffered_();
52625
52626          if (typeof segmentInfo.timestampOffset === 'number') {
52627            // The timestamp offset needs to be regenerated, as the buffer most likely
52628            // changed since the function was added to the queue. This is expected, as the
52629            // load is usually pending the main loader appending new segments.
52630            //
52631            // Note also that the overrideCheck property is set to true. This is because
52632            // isPendingTimestampOffset is set back to false after the first set of the
52633            // timestamp offset (before it was added to the queue). But the presence of
52634            // timestamp offset as a property of segmentInfo serves as enough evidence that
52635            // it should be regenerated.
52636            segmentInfo.timestampOffset = timestampOffsetForSegment({
52637              segmentTimeline: segmentInfo.timeline,
52638              currentTimeline: _this5.currentTimeline_,
52639              startOfSegment: segmentInfo.startOfSegment,
52640              buffered: buffered,
52641              overrideCheck: true
52642            });
52643          }
52644
52645          delete segmentInfo.audioAppendStart;
52646
52647          var audioBuffered = _this5.sourceUpdater_.audioBuffered();
52648
52649          if (audioBuffered.length) {
52650            // Because the audio timestamp offset may have been changed by the main loader,
52651            // the audioAppendStart should be regenerated.
52652            //
52653            // Since the transmuxer is using the actual timing values, but the buffer is
52654            // adjusted by the timestamp offset, the value must be adjusted.
52655            segmentInfo.audioAppendStart = audioBuffered.end(audioBuffered.length - 1) - _this5.sourceUpdater_.audioTimestampOffset();
52656          }
52657
52658          _this5.updateTransmuxerAndRequestSegment_(segmentInfo);
52659        });
52660        return;
52661      }
52662
52663      this.updateTransmuxerAndRequestSegment_(segmentInfo);
52664    };
52665
52666    _proto.updateTransmuxerAndRequestSegment_ = function updateTransmuxerAndRequestSegment_(segmentInfo) {
52667      // We'll update the source buffer's timestamp offset once we have transmuxed data, but
52668      // the transmuxer still needs to be updated before then.
52669      //
52670      // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
52671      // offset must be passed to the transmuxer for stream correcting adjustments.
52672      if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
52673        this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
52674
52675        segmentInfo.gopsToAlignWith = [];
52676        this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
52677
52678        this.transmuxer_.postMessage({
52679          action: 'reset'
52680        });
52681        this.transmuxer_.postMessage({
52682          action: 'setTimestampOffset',
52683          timestampOffset: segmentInfo.timestampOffset
52684        });
52685      }
52686
52687      var simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
52688      segmentInfo.abortRequests = mediaSegmentRequest({
52689        xhr: this.vhs_.xhr,
52690        xhrOptions: this.xhrOptions_,
52691        decryptionWorker: this.decrypter_,
52692        segment: simpleSegment,
52693        handlePartialData: this.handlePartialData_,
52694        abortFn: this.handleAbort_.bind(this),
52695        progressFn: this.handleProgress_.bind(this),
52696        trackInfoFn: this.handleTrackInfo_.bind(this),
52697        timingInfoFn: this.handleTimingInfo_.bind(this),
52698        videoSegmentTimingInfoFn: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId),
52699        captionsFn: this.handleCaptions_.bind(this),
52700        id3Fn: this.handleId3_.bind(this),
52701        dataFn: this.handleData_.bind(this),
52702        doneFn: this.segmentRequestFinished_.bind(this)
52703      });
52704    }
52705    /**
52706     * trim the back buffer so that we don't have too much data
52707     * in the source buffer
52708     *
52709     * @private
52710     *
52711     * @param {Object} segmentInfo - the current segment
52712     */
52713    ;
52714
52715    _proto.trimBackBuffer_ = function trimBackBuffer_(segmentInfo) {
52716      var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
52717      // buffer and a very conservative "garbage collector"
52718      // We manually clear out the old buffer to ensure
52719      // we don't trigger the QuotaExceeded error
52720      // on the source buffer during subsequent appends
52721
52722      if (removeToTime > 0) {
52723        this.remove(0, removeToTime);
52724      }
52725    }
52726    /**
52727     * created a simplified copy of the segment object with just the
52728     * information necessary to perform the XHR and decryption
52729     *
52730     * @private
52731     *
52732     * @param {Object} segmentInfo - the current segment
52733     * @return {Object}
vendor: 57,728 bytes, lines 52733-54399
52733 a simplified segment object copy
52734     */
52735    ;
52736
52737    _proto.createSimplifiedSegmentObj_ = function createSimplifiedSegmentObj_(segmentInfo) {
52738      var segment = segmentInfo.segment;
52739      var simpleSegment = {
52740        resolvedUri: segment.resolvedUri,
52741        byterange: segment.byterange,
52742        requestId: segmentInfo.requestId,
52743        transmuxer: segmentInfo.transmuxer,
52744        audioAppendStart: segmentInfo.audioAppendStart,
52745        gopsToAlignWith: segmentInfo.gopsToAlignWith
52746      };
52747      var previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
52748
52749      if (previousSegment && previousSegment.end && previousSegment.timeline === segment.timeline) {
52750        simpleSegment.baseStartTime = previousSegment.end + segmentInfo.timestampOffset;
52751      }
52752
52753      if (segment.key) {
52754        // if the media sequence is greater than 2^32, the IV will be incorrect
52755        // assuming 10s segments, that would be about 1300 years
52756        var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
52757        simpleSegment.key = this.segmentKey(segment.key);
52758        simpleSegment.key.iv = iv;
52759      }
52760
52761      if (segment.map) {
52762        simpleSegment.map = this.initSegmentForMap(segment.map);
52763      }
52764
52765      return simpleSegment;
52766    };
52767
52768    _proto.saveTransferStats_ = function saveTransferStats_(stats) {
52769      // every request counts as a media request even if it has been aborted
52770      // or canceled due to a timeout
52771      this.mediaRequests += 1;
52772
52773      if (stats) {
52774        this.mediaBytesTransferred += stats.bytesReceived;
52775        this.mediaTransferDuration += stats.roundTripTime;
52776      }
52777    };
52778
52779    _proto.saveBandwidthRelatedStats_ = function saveBandwidthRelatedStats_(stats) {
52780      this.bandwidth = stats.bandwidth;
52781      this.roundTrip = stats.roundTripTime; // byteLength will be used for throughput, and should be based on bytes receieved,
52782      // which we only know at the end of the request and should reflect total bytes
52783      // downloaded rather than just bytes processed from components of the segment
52784
52785      this.pendingSegment_.byteLength = stats.bytesReceived;
52786    };
52787
52788    _proto.handleTimeout_ = function handleTimeout_() {
52789      // although the VTT segment loader bandwidth isn't really used, it's good to
52790      // maintain functinality between segment loaders
52791      this.mediaRequestsTimedout += 1;
52792      this.bandwidth = 1;
52793      this.roundTrip = NaN;
52794      this.trigger('bandwidthupdate');
52795    }
52796    /**
52797     * Handle the callback from the segmentRequest function and set the
52798     * associated SegmentLoader state and errors if necessary
52799     *
52800     * @private
52801     */
52802    ;
52803
52804    _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
52805      // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
52806      // check the call queue directly since this function doesn't need to deal with any
52807      // data, and can continue even if the source buffers are not set up and we didn't get
52808      // any data from the segment
52809      if (this.callQueue_.length) {
52810        this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
52811        return;
52812      }
52813
52814      this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
52815
52816      if (!this.pendingSegment_) {
52817        return;
52818      } // the request was aborted and the SegmentLoader has already started
52819      // another request. this can happen when the timeout for an aborted
52820      // request triggers due to a limitation in the XHR library
52821      // do not count this as any sort of request or we risk double-counting
52822
52823
52824      if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
52825        return;
52826      } // an error occurred from the active pendingSegment_ so reset everything
52827
52828
52829      if (error) {
52830        this.pendingSegment_ = null;
52831        this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
52832
52833        if (error.code === REQUEST_ERRORS.ABORTED) {
52834          return;
52835        }
52836
52837        this.pause(); // the error is really just that at least one of the requests timed-out
52838        // set the bandwidth to a very low value and trigger an ABR switch to
52839        // take emergency action
52840
52841        if (error.code === REQUEST_ERRORS.TIMEOUT) {
52842          this.handleTimeout_();
52843          return;
52844        } // if control-flow has arrived here, then the error is real
52845        // emit an error event to blacklist the current playlist
52846
52847
52848        this.mediaRequestsErrored += 1;
52849        this.error(error);
52850        this.trigger('error');
52851        return;
52852      } // the response was a success so set any bandwidth stats the request
52853      // generated for ABR purposes
52854
52855
52856      this.saveBandwidthRelatedStats_(simpleSegment.stats);
52857      var segmentInfo = this.pendingSegment_;
52858      segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
52859
52860      if (result.gopInfo) {
52861        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
52862      } // Although we may have already started appending on progress, we shouldn't switch the
52863      // state away from loading until we are officially done loading the segment data.
52864
52865
52866      this.state = 'APPENDING';
52867      var isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist);
52868      var isWalkingForward = this.mediaIndex !== null;
52869      var isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ && // TODO verify this behavior
52870      // currentTimeline starts at -1, but we shouldn't end the timeline switching to 0,
52871      // the first timeline
52872      segmentInfo.timeline > 0;
52873
52874      if (!segmentInfo.isFmp4 && (isEndOfStream || isWalkingForward && isDiscontinuity)) {
52875        segmentTransmuxer.endTimeline(this.transmuxer_);
52876      } // used for testing
52877
52878
52879      this.trigger('appending');
52880      this.waitForAppendsToComplete_(segmentInfo);
52881    };
52882
52883    _proto.setTimeMapping_ = function setTimeMapping_(timeline) {
52884      var timelineMapping = this.syncController_.mappingForTimeline(timeline);
52885
52886      if (timelineMapping !== null) {
52887        this.timeMapping_ = timelineMapping;
52888      }
52889    };
52890
52891    _proto.updateMediaSecondsLoaded_ = function updateMediaSecondsLoaded_(segment) {
52892      if (typeof segment.start === 'number' && typeof segment.end === 'number') {
52893        this.mediaSecondsLoaded += segment.end - segment.start;
52894      } else {
52895        this.mediaSecondsLoaded += segment.duration;
52896      }
52897    };
52898
52899    _proto.shouldUpdateTransmuxerTimestampOffset_ = function shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
52900      if (timestampOffset === null) {
52901        return false;
52902      } // note that we're potentially using the same timestamp offset for both video and
52903      // audio
52904
52905
52906      if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
52907        return true;
52908      }
52909
52910      if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
52911        return true;
52912      }
52913
52914      return false;
52915    };
52916
52917    _proto.trueSegmentStart_ = function trueSegmentStart_(_ref5) {
52918      var currentStart = _ref5.currentStart,
52919          playlist = _ref5.playlist,
52920          mediaIndex = _ref5.mediaIndex,
52921          firstVideoFrameTimeForData = _ref5.firstVideoFrameTimeForData,
52922          currentVideoTimestampOffset = _ref5.currentVideoTimestampOffset,
52923          useVideoTimingInfo = _ref5.useVideoTimingInfo,
52924          videoTimingInfo = _ref5.videoTimingInfo,
52925          audioTimingInfo = _ref5.audioTimingInfo;
52926
52927      if (typeof currentStart !== 'undefined') {
52928        // if start was set once, keep using it
52929        return currentStart;
52930      }
52931
52932      if (!useVideoTimingInfo) {
52933        return audioTimingInfo.start;
52934      }
52935
52936      var previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
52937      // within that segment. Since the transmuxer maintains a cache of incomplete data
52938      // from and/or the last frame seen, the start time may reflect a frame that starts
52939      // in the previous segment. Check for that case and ensure the start time is
52940      // accurate for the segment.
52941
52942      if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
52943        return firstVideoFrameTimeForData;
52944      }
52945
52946      return videoTimingInfo.start;
52947    };
52948
52949    _proto.waitForAppendsToComplete_ = function waitForAppendsToComplete_(segmentInfo) {
52950      if (!this.currentMediaInfo_) {
52951        this.error({
52952          message: 'No starting media returned, likely due to an unsupported media format.',
52953          blacklistDuration: Infinity
52954        });
52955        this.trigger('error');
52956        return;
52957      } // Although transmuxing is done, appends may not yet be finished. Throw a marker
52958      // on each queue this loader is responsible for to ensure that the appends are
52959      // complete.
52960
52961
52962      var _this$currentMediaInf2 = this.currentMediaInfo_,
52963          hasAudio = _this$currentMediaInf2.hasAudio,
52964          hasVideo = _this$currentMediaInf2.hasVideo,
52965          isMuxed = _this$currentMediaInf2.isMuxed;
52966      var waitForVideo = this.loaderType_ === 'main' && hasVideo; // TODO: does this break partial support for muxed content?
52967
52968      var waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
52969      segmentInfo.waitingOnAppends = 0; // segments with no data
52970
52971      if (!segmentInfo.hasAppendedData_) {
52972        if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
52973          // When there's no audio or video data in the segment, there's no audio or video
52974          // timing information.
52975          //
52976          // If there's no audio or video timing information, then the timestamp offset
52977          // can't be adjusted to the appropriate value for the transmuxer and source
52978          // buffers.
52979          //
52980          // Therefore, the next segment should be used to set the timestamp offset.
52981          this.isPendingTimestampOffset_ = true;
52982        } // override settings for metadata only segments
52983
52984
52985        segmentInfo.timingInfo = {
52986          start: 0
52987        };
52988        segmentInfo.waitingOnAppends++;
52989
52990        if (!this.isPendingTimestampOffset_) {
52991          // update the timestampoffset
52992          this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
52993          // no video/audio data.
52994
52995          this.processMetadataQueue_();
52996        } // append is "done" instantly with no data.
52997
52998
52999        this.checkAppendsDone_(segmentInfo);
53000        return;
53001      } // Since source updater could call back synchronously, do the increments first.
53002
53003
53004      if (waitForVideo) {
53005        segmentInfo.waitingOnAppends++;
53006      }
53007
53008      if (waitForAudio) {
53009        segmentInfo.waitingOnAppends++;
53010      }
53011
53012      if (waitForVideo) {
53013        this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
53014      }
53015
53016      if (waitForAudio) {
53017        this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
53018      }
53019    };
53020
53021    _proto.checkAppendsDone_ = function checkAppendsDone_(segmentInfo) {
53022      if (this.checkForAbort_(segmentInfo.requestId)) {
53023        return;
53024      }
53025
53026      segmentInfo.waitingOnAppends--;
53027
53028      if (segmentInfo.waitingOnAppends === 0) {
53029        this.handleAppendsDone_();
53030      }
53031    };
53032
53033    _proto.checkForIllegalMediaSwitch = function checkForIllegalMediaSwitch(trackInfo) {
53034      var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.currentMediaInfo_, trackInfo);
53035
53036      if (illegalMediaSwitchError) {
53037        this.error({
53038          message: illegalMediaSwitchError,
53039          blacklistDuration: Infinity
53040        });
53041        this.trigger('error');
53042        return true;
53043      }
53044
53045      return false;
53046    };
53047
53048    _proto.updateSourceBufferTimestampOffset_ = function updateSourceBufferTimestampOffset_(segmentInfo) {
53049      if (segmentInfo.timestampOffset === null || // we don't yet have the start for whatever media type (video or audio) has
53050      // priority, timing-wise, so we must wait
53051      typeof segmentInfo.timingInfo.start !== 'number' || // already updated the timestamp offset for this segment
53052      segmentInfo.changedTimestampOffset || // the alt audio loader should not be responsible for setting the timestamp offset
53053      this.loaderType_ !== 'main') {
53054        return;
53055      }
53056
53057      var didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
53058      // the timing info here comes from video. In the event that the audio is longer than
53059      // the video, this will trim the start of the audio.
53060      // This also trims any offset from 0 at the beginning of the media
53061
53062      segmentInfo.timestampOffset -= segmentInfo.timingInfo.start; // In the event that there are partial segment downloads, each will try to update the
53063      // timestamp offset. Retaining this bit of state prevents us from updating in the
53064      // future (within the same segment), however, there may be a better way to handle it.
53065
53066      segmentInfo.changedTimestampOffset = true;
53067
53068      if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
53069        this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
53070        didChange = true;
53071      }
53072
53073      if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
53074        this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
53075        didChange = true;
53076      }
53077
53078      if (didChange) {
53079        this.trigger('timestampoffset');
53080      }
53081    };
53082
53083    _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_(segmentInfo) {
53084      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
53085      var useVideoTimingInfo = this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo;
53086      var prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
53087
53088      if (!prioritizedTimingInfo) {
53089        return;
53090      }
53091
53092      segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ? // End time may not exist in a case where we aren't parsing the full segment (one
53093      // current example is the case of fmp4), so use the rough duration to calculate an
53094      // end time.
53095      prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
53096    }
53097    /**
53098     * callback to run when appendBuffer is finished. detects if we are
53099     * in a good state to do things with the data we got, or if we need
53100     * to wait for more
53101     *
53102     * @private
53103     */
53104    ;
53105
53106    _proto.handleAppendsDone_ = function handleAppendsDone_() {
53107      // appendsdone can cause an abort
53108      if (this.pendingSegment_) {
53109        this.trigger('appendsdone');
53110      }
53111
53112      if (!this.pendingSegment_) {
53113        this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
53114        // all appending cases?
53115
53116        if (!this.paused()) {
53117          this.monitorBuffer_();
53118        }
53119
53120        return;
53121      }
53122
53123      var segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's
53124      // best to wait until all appends are done so we're sure that the primary media is
53125      // finished (and we have its end time).
53126
53127      this.updateTimingInfoEnd_(segmentInfo);
53128
53129      if (this.shouldSaveSegmentTimingInfo_) {
53130        // Timeline mappings should only be saved for the main loader. This is for multiple
53131        // reasons:
53132        //
53133        // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
53134        //    and the main loader try to save the timeline mapping, whichever comes later
53135        //    will overwrite the first. In theory this is OK, as the mappings should be the
53136        //    same, however, it breaks for (2)
53137        // 2) In the event of a live stream, the initial live point will make for a somewhat
53138        //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
53139        //    the mapping will be off for one of the streams, dependent on which one was
53140        //    first saved (see (1)).
53141        // 3) Primary timing goes by video in VHS, so the mapping should be video.
53142        //
53143        // Since the audio loader will wait for the main loader to load the first segment,
53144        // the main loader will save the first timeline mapping, and ensure that there won't
53145        // be a case where audio loads two segments without saving a mapping (thus leading
53146        // to missing segment timing info).
53147        this.syncController_.saveSegmentTimingInfo({
53148          segmentInfo: segmentInfo,
53149          shouldSaveTimelineMapping: this.loaderType_ === 'main'
53150        });
53151      }
53152
53153      this.logger_(segmentInfoString(segmentInfo));
53154      this.recordThroughput_(segmentInfo);
53155      this.pendingSegment_ = null;
53156      this.state = 'READY'; // TODO minor, but for partial segment downloads, this can be done earlier to save
53157      // on bandwidth and download time
53158
53159      if (segmentInfo.isSyncRequest) {
53160        this.trigger('syncinfoupdate');
53161        return;
53162      }
53163
53164      this.addSegmentMetadataCue_(segmentInfo);
53165      this.fetchAtBuffer_ = true;
53166
53167      if (this.currentTimeline_ !== segmentInfo.timeline) {
53168        this.timelineChangeController_.lastTimelineChange({
53169          type: this.loaderType_,
53170          from: this.currentTimeline_,
53171          to: segmentInfo.timeline
53172        }); // If audio is not disabled, the main segment loader is responsible for updating
53173        // the audio timeline as well. If the content is video only, this won't have any
53174        // impact.
53175
53176        if (this.loaderType_ === 'main' && !this.audioDisabled_) {
53177          this.timelineChangeController_.lastTimelineChange({
53178            type: 'audio',
53179            from: this.currentTimeline_,
53180            to: segmentInfo.timeline
53181          });
53182        }
53183      }
53184
53185      this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
53186      // the following conditional otherwise it may consider this a bad "guess"
53187      // and attempt to resync when the post-update seekable window and live
53188      // point would mean that this was the perfect segment to fetch
53189
53190      this.trigger('syncinfoupdate');
53191      var segment = segmentInfo.segment; // If we previously appended a segment that ends more than 3 targetDurations before
53192      // the currentTime_ that means that our conservative guess was too conservative.
53193      // In that case, reset the loader state so that we try to use any information gained
53194      // from the previous request to create a new, more accurate, sync-point.
53195
53196      if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
53197        this.resetEverything();
53198        return;
53199      }
53200
53201      var isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
53202      // and conservatively guess
53203
53204      if (isWalkingForward) {
53205        this.trigger('bandwidthupdate');
53206      }
53207
53208      this.trigger('progress');
53209      this.mediaIndex = segmentInfo.mediaIndex; // any time an update finishes and the last segment is in the
53210      // buffer, end the stream. this ensures the "ended" event will
53211      // fire if playback reaches that point.
53212
53213      if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist)) {
53214        this.endOfStream();
53215      } // used for testing
53216
53217
53218      this.trigger('appended');
53219
53220      if (!this.paused()) {
53221        this.monitorBuffer_();
53222      }
53223    }
53224    /**
53225     * Records the current throughput of the decrypt, transmux, and append
53226     * portion of the semgment pipeline. `throughput.rate` is a the cumulative
53227     * moving average of the throughput. `throughput.count` is the number of
53228     * data points in the average.
53229     *
53230     * @private
53231     * @param {Object} segmentInfo the object returned by loadSegment
53232     */
53233    ;
53234
53235    _proto.recordThroughput_ = function recordThroughput_(segmentInfo) {
53236      var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
53237      // by zero in the case where the throughput is ridiculously high
53238
53239      var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
53240
53241      var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
53242      //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
53243
53244      this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
53245    }
53246    /**
53247     * Adds a cue to the segment-metadata track with some metadata information about the
53248     * segment
53249     *
53250     * @private
53251     * @param {Object} segmentInfo
53252     *        the object returned by loadSegment
53253     * @method addSegmentMetadataCue_
53254     */
53255    ;
53256
53257    _proto.addSegmentMetadataCue_ = function addSegmentMetadataCue_(segmentInfo) {
53258      if (!this.segmentMetadataTrack_) {
53259        return;
53260      }
53261
53262      var segment = segmentInfo.segment;
53263      var start = segment.start;
53264      var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
53265
53266      if (!finite(start) || !finite(end)) {
53267        return;
53268      }
53269
53270      removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
53271      var Cue = window$1.WebKitDataCue || window$1.VTTCue;
53272      var value = {
53273        custom: segment.custom,
53274        dateTimeObject: segment.dateTimeObject,
53275        dateTimeString: segment.dateTimeString,
53276        bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
53277        resolution: segmentInfo.playlist.attributes.RESOLUTION,
53278        codecs: segmentInfo.playlist.attributes.CODECS,
53279        byteLength: segmentInfo.byteLength,
53280        uri: segmentInfo.uri,
53281        timeline: segmentInfo.timeline,
53282        playlist: segmentInfo.playlist.id,
53283        start: start,
53284        end: end
53285      };
53286      var data = JSON.stringify(value);
53287      var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
53288      // the differences of WebKitDataCue in safari and VTTCue in other browsers
53289
53290      cue.value = value;
53291      this.segmentMetadataTrack_.addCue(cue);
53292    };
53293
53294    return SegmentLoader;
53295  }(videojs$1.EventTarget);
53296
53297  function noop$1() {}
53298
53299  var toTitleCase$1 = function toTitleCase(string) {
53300    if (typeof string !== 'string') {
53301      return string;
53302    }
53303
53304    return string.replace(/./, function (w) {
53305      return w.toUpperCase();
53306    });
53307  };
53308
53309  var bufferTypes = ['video', 'audio'];
53310
53311  var _updating = function updating(type, sourceUpdater) {
53312    var sourceBuffer = sourceUpdater[type + "Buffer"];
53313    return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
53314  };
53315
53316  var nextQueueIndexOfType = function nextQueueIndexOfType(type, queue) {
53317    for (var i = 0; i < queue.length; i++) {
53318      var queueEntry = queue[i];
53319
53320      if (queueEntry.type === 'mediaSource') {
53321        // If the next entry is a media source entry (uses multiple source buffers), block
53322        // processing to allow it to go through first.
53323        return null;
53324      }
53325
53326      if (queueEntry.type === type) {
53327        return i;
53328      }
53329    }
53330
53331    return null;
53332  };
53333
53334  var shiftQueue = function shiftQueue(type, sourceUpdater) {
53335    if (sourceUpdater.queue.length === 0) {
53336      return;
53337    }
53338
53339    var queueIndex = 0;
53340    var queueEntry = sourceUpdater.queue[queueIndex];
53341
53342    if (queueEntry.type === 'mediaSource') {
53343      if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
53344        sourceUpdater.queue.shift();
53345        queueEntry.action(sourceUpdater);
53346
53347        if (queueEntry.doneFn) {
53348          queueEntry.doneFn();
53349        } // Only specific source buffer actions must wait for async updateend events. Media
53350        // Source actions process synchronously. Therefore, both audio and video source
53351        // buffers are now clear to process the next queue entries.
53352
53353
53354        shiftQueue('audio', sourceUpdater);
53355        shiftQueue('video', sourceUpdater);
53356      } // Media Source actions require both source buffers, so if the media source action
53357      // couldn't process yet (because one or both source buffers are busy), block other
53358      // queue actions until both are available and the media source action can process.
53359
53360
53361      return;
53362    }
53363
53364    if (type === 'mediaSource') {
53365      // If the queue was shifted by a media source action (this happens when pushing a
53366      // media source action onto the queue), then it wasn't from an updateend event from an
53367      // audio or video source buffer, so there's no change from previous state, and no
53368      // processing should be done.
53369      return;
53370    } // Media source queue entries don't need to consider whether the source updater is
53371    // started (i.e., source buffers are created) as they don't need the source buffers, but
53372    // source buffer queue entries do.
53373
53374
53375    if (!sourceUpdater.started_ || sourceUpdater.mediaSource.readyState === 'closed' || _updating(type, sourceUpdater)) {
53376      return;
53377    }
53378
53379    if (queueEntry.type !== type) {
53380      queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
53381
53382      if (queueIndex === null) {
53383        // Either there's no queue entry that uses this source buffer type in the queue, or
53384        // there's a media source queue entry before the next entry of this type, in which
53385        // case wait for that action to process first.
53386        return;
53387      }
53388
53389      queueEntry = sourceUpdater.queue[queueIndex];
53390    }
53391
53392    sourceUpdater.queue.splice(queueIndex, 1);
53393    queueEntry.action(type, sourceUpdater);
53394
53395    if (!queueEntry.doneFn) {
53396      // synchronous operation, process next entry
53397      shiftQueue(type, sourceUpdater);
53398      return;
53399    } // asynchronous operation, so keep a record that this source buffer type is in use
53400
53401
53402    sourceUpdater.queuePending[type] = queueEntry;
53403  };
53404
53405  var cleanupBuffer = function cleanupBuffer(type, sourceUpdater) {
53406    var buffer = sourceUpdater[type + "Buffer"];
53407    var titleType = toTitleCase$1(type);
53408
53409    if (!buffer) {
53410      return;
53411    }
53412
53413    buffer.removeEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
53414    buffer.removeEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
53415    sourceUpdater.codecs[type] = null;
53416    sourceUpdater[type + "Buffer"] = null;
53417  };
53418
53419  var inSourceBuffers = function inSourceBuffers(mediaSource, sourceBuffer) {
53420    return mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
53421  };
53422
53423  var actions = {
53424    appendBuffer: function appendBuffer(bytes, segmentInfo) {
53425      return function (type, sourceUpdater) {
53426        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
53427        // or the media source does not contain this source buffer.
53428
53429        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53430          return;
53431        }
53432
53433        sourceUpdater.logger_("Appending segment " + segmentInfo.mediaIndex + "'s " + bytes.length + " bytes to " + type + "Buffer");
53434        sourceBuffer.appendBuffer(bytes);
53435      };
53436    },
53437    remove: function remove(start, end) {
53438      return function (type, sourceUpdater) {
53439        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
53440        // or the media source does not contain this source buffer.
53441
53442        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53443          return;
53444        }
53445
53446        sourceUpdater.logger_("Removing " + start + " to " + end + " from " + type + "Buffer");
53447        sourceBuffer.remove(start, end);
53448      };
53449    },
53450    timestampOffset: function timestampOffset(offset) {
53451      return function (type, sourceUpdater) {
53452        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
53453        // or the media source does not contain this source buffer.
53454
53455        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53456          return;
53457        }
53458
53459        sourceUpdater.logger_("Setting " + type + "timestampOffset to " + offset);
53460        sourceBuffer.timestampOffset = offset;
53461      };
53462    },
53463    callback: function callback(_callback) {
53464      return function (type, sourceUpdater) {
53465        _callback();
53466      };
53467    },
53468    endOfStream: function endOfStream(error) {
53469      return function (sourceUpdater) {
53470        if (sourceUpdater.mediaSource.readyState !== 'open') {
53471          return;
53472        }
53473
53474        sourceUpdater.logger_("Calling mediaSource endOfStream(" + (error || '') + ")");
53475
53476        try {
53477          sourceUpdater.mediaSource.endOfStream(error);
53478        } catch (e) {
53479          videojs$1.log.warn('Failed to call media source endOfStream', e);
53480        }
53481      };
53482    },
53483    duration: function duration(_duration) {
53484      return function (sourceUpdater) {
53485        sourceUpdater.logger_("Setting mediaSource duration to " + _duration);
53486
53487        try {
53488          sourceUpdater.mediaSource.duration = _duration;
53489        } catch (e) {
53490          videojs$1.log.warn('Failed to set media source duration', e);
53491        }
53492      };
53493    },
53494    abort: function abort() {
53495      return function (type, sourceUpdater) {
53496        if (sourceUpdater.mediaSource.readyState !== 'open') {
53497          return;
53498        }
53499
53500        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
53501        // or the media source does not contain this source buffer.
53502
53503        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53504          return;
53505        }
53506
53507        sourceUpdater.logger_("calling abort on " + type + "Buffer");
53508
53509        try {
53510          sourceBuffer.abort();
53511        } catch (e) {
53512          videojs$1.log.warn("Failed to abort on " + type + "Buffer", e);
53513        }
53514      };
53515    },
53516    addSourceBuffer: function addSourceBuffer(type, codec) {
53517      return function (sourceUpdater) {
53518        var titleType = toTitleCase$1(type);
53519        var mime = codecs_5(codec);
53520        sourceUpdater.logger_("Adding " + type + "Buffer with codec " + codec + " to mediaSource");
53521        var sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
53522        sourceBuffer.addEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
53523        sourceBuffer.addEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
53524        sourceUpdater.codecs[type] = codec;
53525        sourceUpdater[type + "Buffer"] = sourceBuffer;
53526      };
53527    },
53528    removeSourceBuffer: function removeSourceBuffer(type) {
53529      return function (sourceUpdater) {
53530        var sourceBuffer = sourceUpdater[type + "Buffer"];
53531        cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
53532        // or the media source does not contain this source buffer.
53533
53534        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53535          return;
53536        }
53537
53538        sourceUpdater.logger_("Removing " + type + "Buffer with codec " + sourceUpdater.codecs[type] + " from mediaSource");
53539
53540        try {
53541          sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
53542        } catch (e) {
53543          videojs$1.log.warn("Failed to removeSourceBuffer " + type + "Buffer", e);
53544        }
53545      };
53546    },
53547    changeType: function changeType(codec) {
53548      return function (type, sourceUpdater) {
53549        var sourceBuffer = sourceUpdater[type + "Buffer"];
53550        var mime = codecs_5(codec); // can't do anything if the media source / source buffer is null
53551        // or the media source does not contain this source buffer.
53552
53553        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
53554          return;
53555        } // do not update codec if we don't need to.
53556
53557
53558        if (sourceUpdater.codecs[type] === codec) {
53559          return;
53560        }
53561
53562        sourceUpdater.logger_("changing " + type + "Buffer codec from " + sourceUpdater.codecs[type] + " to " + codec);
53563        sourceBuffer.changeType(mime);
53564        sourceUpdater.codecs[type] = codec;
53565      };
53566    }
53567  };
53568
53569  var pushQueue = function pushQueue(_ref) {
53570    var type = _ref.type,
53571        sourceUpdater = _ref.sourceUpdater,
53572        action = _ref.action,
53573        doneFn = _ref.doneFn,
53574        name = _ref.name;
53575    sourceUpdater.queue.push({
53576      type: type,
53577      action: action,
53578      doneFn: doneFn,
53579      name: name
53580    });
53581    shiftQueue(type, sourceUpdater);
53582  };
53583
53584  var onUpdateend = function onUpdateend(type, sourceUpdater) {
53585    return function (e) {
53586      // Although there should, in theory, be a pending action for any updateend receieved,
53587      // there are some actions that may trigger updateend events without set definitions in
53588      // the w3c spec. For instance, setting the duration on the media source may trigger
53589      // updateend events on source buffers. This does not appear to be in the spec. As such,
53590      // if we encounter an updateend without a corresponding pending action from our queue
53591      // for that source buffer type, process the next action.
53592      if (sourceUpdater.queuePending[type]) {
53593        var doneFn = sourceUpdater.queuePending[type].doneFn;
53594        sourceUpdater.queuePending[type] = null;
53595
53596        if (doneFn) {
53597          // if there's an error, report it
53598          doneFn(sourceUpdater[type + "Error_"]);
53599        }
53600      }
53601
53602      shiftQueue(type, sourceUpdater);
53603    };
53604  };
53605  /**
53606   * A queue of callbacks to be serialized and applied when a
53607   * MediaSource and its associated SourceBuffers are not in the
53608   * updating state. It is used by the segment loader to update the
53609   * underlying SourceBuffers when new data is loaded, for instance.
53610   *
53611   * @class SourceUpdater
53612   * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
53613   * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
53614   */
53615
53616
53617  var SourceUpdater = /*#__PURE__*/function (_videojs$EventTarget) {
53618    inheritsLoose(SourceUpdater, _videojs$EventTarget);
53619
53620    function SourceUpdater(mediaSource) {
53621      var _this;
53622
53623      _this = _videojs$EventTarget.call(this) || this;
53624      _this.mediaSource = mediaSource;
53625
53626      _this.sourceopenListener_ = function () {
53627        return shiftQueue('mediaSource', assertThisInitialized(_this));
53628      };
53629
53630      _this.mediaSource.addEventListener('sourceopen', _this.sourceopenListener_);
53631
53632      _this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
53633
53634      _this.audioTimestampOffset_ = 0;
53635      _this.videoTimestampOffset_ = 0;
53636      _this.queue = [];
53637      _this.queuePending = {
53638        audio: null,
53639        video: null
53640      };
53641      _this.delayedAudioAppendQueue_ = [];
53642      _this.videoAppendQueued_ = false;
53643      _this.codecs = {};
53644      _this.onVideoUpdateEnd_ = onUpdateend('video', assertThisInitialized(_this));
53645      _this.onAudioUpdateEnd_ = onUpdateend('audio', assertThisInitialized(_this));
53646
53647      _this.onVideoError_ = function (e) {
53648        // used for debugging
53649        _this.videoError_ = e;
53650      };
53651
53652      _this.onAudioError_ = function (e) {
53653        // used for debugging
53654        _this.audioError_ = e;
53655      };
53656
53657      _this.started_ = false;
53658      return _this;
53659    }
53660
53661    var _proto = SourceUpdater.prototype;
53662
53663    _proto.ready = function ready() {
53664      return this.started_;
53665    };
53666
53667    _proto.createSourceBuffers = function createSourceBuffers(codecs) {
53668      if (this.ready()) {
53669        // already created them before
53670        return;
53671      } // the intial addOrChangeSourceBuffers will always be
53672      // two add buffers.
53673
53674
53675      this.addOrChangeSourceBuffers(codecs);
53676      this.started_ = true;
53677      this.trigger('ready');
53678    }
53679    /**
53680     * Add a type of source buffer to the media source.
53681     *
53682     * @param {string} type
53683     *        The type of source buffer to add.
53684     *
53685     * @param {string} codec
53686     *        The codec to add the source buffer with.
53687     */
53688    ;
53689
53690    _proto.addSourceBuffer = function addSourceBuffer(type, codec) {
53691      pushQueue({
53692        type: 'mediaSource',
53693        sourceUpdater: this,
53694        action: actions.addSourceBuffer(type, codec),
53695        name: 'addSourceBuffer'
53696      });
53697    }
53698    /**
53699     * call abort on a source buffer.
53700     *
53701     * @param {string} type
53702     *        The type of source buffer to call abort on.
53703     */
53704    ;
53705
53706    _proto.abort = function abort(type) {
53707      pushQueue({
53708        type: type,
53709        sourceUpdater: this,
53710        action: actions.abort(type),
53711        name: 'abort'
53712      });
53713    }
53714    /**
53715     * Call removeSourceBuffer and remove a specific type
53716     * of source buffer on the mediaSource.
53717     *
53718     * @param {string} type
53719     *        The type of source buffer to remove.
53720     */
53721    ;
53722
53723    _proto.removeSourceBuffer = function removeSourceBuffer(type) {
53724      if (!this.canRemoveSourceBuffer()) {
53725        videojs$1.log.error('removeSourceBuffer is not supported!');
53726        return;
53727      }
53728
53729      pushQueue({
53730        type: 'mediaSource',
53731        sourceUpdater: this,
53732        action: actions.removeSourceBuffer(type),
53733        name: 'removeSourceBuffer'
53734      });
53735    }
53736    /**
53737     * Whether or not the removeSourceBuffer function is supported
53738     * on the mediaSource.
53739     *
53740     * @return {boolean}
53741     *          if removeSourceBuffer can be called.
53742     */
53743    ;
53744
53745    _proto.canRemoveSourceBuffer = function canRemoveSourceBuffer() {
53746      // IE reports that it supports removeSourceBuffer, but often throws
53747      // errors when attempting to use the function. So we report that it
53748      // does not support removeSourceBuffer.
53749      return !videojs$1.browser.IE_VERSION && window$1.MediaSource && window$1.MediaSource.prototype && typeof window$1.MediaSource.prototype.removeSourceBuffer === 'function';
53750    }
53751    /**
53752     * Whether or not the changeType function is supported
53753     * on our SourceBuffers.
53754     *
53755     * @return {boolean}
53756     *         if changeType can be called.
53757     */
53758    ;
53759
53760    SourceUpdater.canChangeType = function canChangeType() {
53761      return window$1.SourceBuffer && window$1.SourceBuffer.prototype && typeof window$1.SourceBuffer.prototype.changeType === 'function';
53762    }
53763    /**
53764     * Whether or not the changeType function is supported
53765     * on our SourceBuffers.
53766     *
53767     * @return {boolean}
53768     *         if changeType can be called.
53769     */
53770    ;
53771
53772    _proto.canChangeType = function canChangeType() {
53773      return this.constructor.canChangeType();
53774    }
53775    /**
53776     * Call the changeType function on a source buffer, given the code and type.
53777     *
53778     * @param {string} type
53779     *        The type of source buffer to call changeType on.
53780     *
53781     * @param {string} codec
53782     *        The codec string to change type with on the source buffer.
53783     */
53784    ;
53785
53786    _proto.changeType = function changeType(type, codec) {
53787      if (!this.canChangeType()) {
53788        videojs$1.log.error('changeType is not supported!');
53789        return;
53790      }
53791
53792      pushQueue({
53793        type: type,
53794        sourceUpdater: this,
53795        action: actions.changeType(codec),
53796        name: 'changeType'
53797      });
53798    }
53799    /**
53800     * Add source buffers with a codec or, if they are already created,
53801     * call changeType on source buffers using changeType.
53802     *
53803     * @param {Object} codecs
53804     *        Codecs to switch to
53805     */
53806    ;
53807
53808    _proto.addOrChangeSourceBuffers = function addOrChangeSourceBuffers(codecs) {
53809      var _this2 = this;
53810
53811      if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
53812        throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
53813      }
53814
53815      Object.keys(codecs).forEach(function (type) {
53816        var codec = codecs[type];
53817
53818        if (!_this2.ready()) {
53819          return _this2.addSourceBuffer(type, codec);
53820        }
53821
53822        if (_this2.canChangeType()) {
53823          _this2.changeType(type, codec);
53824        }
53825      });
53826    }
53827    /**
53828     * Queue an update to append an ArrayBuffer.
53829     *
53830     * @param {MediaObject} object containing audioBytes and/or videoBytes
53831     * @param {Function} done the function to call when done
53832     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
53833     */
53834    ;
53835
53836    _proto.appendBuffer = function appendBuffer(options, doneFn) {
53837      var _this3 = this;
53838
53839      var segmentInfo = options.segmentInfo,
53840          type = options.type,
53841          bytes = options.bytes;
53842      this.processedAppend_ = true;
53843
53844      if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
53845        this.delayedAudioAppendQueue_.push([options, doneFn]);
53846        this.logger_("delayed audio append of " + bytes.length + " until video append");
53847        return;
53848      }
53849
53850      pushQueue({
53851        type: type,
53852        sourceUpdater: this,
53853        action: actions.appendBuffer(bytes, segmentInfo || {
53854          mediaIndex: -1
53855        }),
53856        doneFn: doneFn,
53857        name: 'appendBuffer'
53858      });
53859
53860      if (type === 'video') {
53861        this.videoAppendQueued_ = true;
53862
53863        if (!this.delayedAudioAppendQueue_.length) {
53864          return;
53865        }
53866
53867        var queue = this.delayedAudioAppendQueue_.slice();
53868        this.logger_("queuing delayed audio " + queue.length + " appendBuffers");
53869        this.delayedAudioAppendQueue_.length = 0;
53870        queue.forEach(function (que) {
53871          _this3.appendBuffer.apply(_this3, que);
53872        });
53873      }
53874    }
53875    /**
53876     * Get the audio buffer's buffered timerange.
53877     *
53878     * @return {TimeRange}
53879     *         The audio buffer's buffered time range
53880     */
53881    ;
53882
53883    _proto.audioBuffered = function audioBuffered() {
53884      // no media source/source buffer or it isn't in the media sources
53885      // source buffer list
53886      if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
53887        return videojs$1.createTimeRange();
53888      }
53889
53890      return this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs$1.createTimeRange();
53891    }
53892    /**
53893     * Get the video buffer's buffered timerange.
53894     *
53895     * @return {TimeRange}
53896     *         The video buffer's buffered time range
53897     */
53898    ;
53899
53900    _proto.videoBuffered = function videoBuffered() {
53901      // no media source/source buffer or it isn't in the media sources
53902      // source buffer list
53903      if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
53904        return videojs$1.createTimeRange();
53905      }
53906
53907      return this.videoBuffer.buffered ? this.videoBuffer.buffered : videojs$1.createTimeRange();
53908    }
53909    /**
53910     * Get a combined video/audio buffer's buffered timerange.
53911     *
53912     * @return {TimeRange}
53913     *         the combined time range
53914     */
53915    ;
53916
53917    _proto.buffered = function buffered() {
53918      var video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
53919      var audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
53920
53921      if (audio && !video) {
53922        return this.audioBuffered();
53923      }
53924
53925      if (video && !audio) {
53926        return this.videoBuffered();
53927      }
53928
53929      return bufferIntersection(this.audioBuffered(), this.videoBuffered());
53930    }
53931    /**
53932     * Add a callback to the queue that will set duration on the mediaSource.
53933     *
53934     * @param {number} duration
53935     *        The duration to set
53936     *
53937     * @param {Function} [doneFn]
53938     *        function to run after duration has been set.
53939     */
53940    ;
53941
53942    _proto.setDuration = function setDuration(duration, doneFn) {
53943      if (doneFn === void 0) {
53944        doneFn = noop$1;
53945      } // In order to set the duration on the media source, it's necessary to wait for all
53946      // source buffers to no longer be updating. "If the updating attribute equals true on
53947      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
53948      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
53949
53950
53951      pushQueue({
53952        type: 'mediaSource',
53953        sourceUpdater: this,
53954        action: actions.duration(duration),
53955        name: 'duration',
53956        doneFn: doneFn
53957      });
53958    }
53959    /**
53960     * Add a mediaSource endOfStream call to the queue
53961     *
53962     * @param {Error} [error]
53963     *        Call endOfStream with an error
53964     *
53965     * @param {Function} [doneFn]
53966     *        A function that should be called when the
53967     *        endOfStream call has finished.
53968     */
53969    ;
53970
53971    _proto.endOfStream = function endOfStream(error, doneFn) {
53972      if (error === void 0) {
53973        error = null;
53974      }
53975
53976      if (doneFn === void 0) {
53977        doneFn = noop$1;
53978      }
53979
53980      if (typeof error !== 'string') {
53981        error = undefined;
53982      } // In order to set the duration on the media source, it's necessary to wait for all
53983      // source buffers to no longer be updating. "If the updating attribute equals true on
53984      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
53985      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
53986
53987
53988      pushQueue({
53989        type: 'mediaSource',
53990        sourceUpdater: this,
53991        action: actions.endOfStream(error),
53992        name: 'endOfStream',
53993        doneFn: doneFn
53994      });
53995    }
53996    /**
53997     * Queue an update to remove a time range from the buffer.
53998     *
53999     * @param {number} start where to start the removal
54000     * @param {number} end where to end the removal
54001     * @param {Function} [done=noop] optional callback to be executed when the remove
54002     * operation is complete
54003     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
54004     */
54005    ;
54006
54007    _proto.removeAudio = function removeAudio(start, end, done) {
54008      if (done === void 0) {
54009        done = noop$1;
54010      }
54011
54012      if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
54013        done();
54014        return;
54015      }
54016
54017      pushQueue({
54018        type: 'audio',
54019        sourceUpdater: this,
54020        action: actions.remove(start, end),
54021        doneFn: done,
54022        name: 'remove'
54023      });
54024    }
54025    /**
54026     * Queue an update to remove a time range from the buffer.
54027     *
54028     * @param {number} start where to start the removal
54029     * @param {number} end where to end the removal
54030     * @param {Function} [done=noop] optional callback to be executed when the remove
54031     * operation is complete
54032     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
54033     */
54034    ;
54035
54036    _proto.removeVideo = function removeVideo(start, end, done) {
54037      if (done === void 0) {
54038        done = noop$1;
54039      }
54040
54041      if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
54042        done();
54043        return;
54044      }
54045
54046      pushQueue({
54047        type: 'video',
54048        sourceUpdater: this,
54049        action: actions.remove(start, end),
54050        doneFn: done,
54051        name: 'remove'
54052      });
54053    }
54054    /**
54055     * Whether the underlying sourceBuffer is updating or not
54056     *
54057     * @return {boolean} the updating status of the SourceBuffer
54058     */
54059    ;
54060
54061    _proto.updating = function updating() {
54062      // the audio/video source buffer is updating
54063      if (_updating('audio', this) || _updating('video', this)) {
54064        return true;
54065      }
54066
54067      return false;
54068    }
54069    /**
54070     * Set/get the timestampoffset on the audio SourceBuffer
54071     *
54072     * @return {number} the timestamp offset
54073     */
54074    ;
54075
54076    _proto.audioTimestampOffset = function audioTimestampOffset(offset) {
54077      if (typeof offset !== 'undefined' && this.audioBuffer && // no point in updating if it's the same
54078      this.audioTimestampOffset_ !== offset) {
54079        pushQueue({
54080          type: 'audio',
54081          sourceUpdater: this,
54082          action: actions.timestampOffset(offset),
54083          name: 'timestampOffset'
54084        });
54085        this.audioTimestampOffset_ = offset;
54086      }
54087
54088      return this.audioTimestampOffset_;
54089    }
54090    /**
54091     * Set/get the timestampoffset on the video SourceBuffer
54092     *
54093     * @return {number} the timestamp offset
54094     */
54095    ;
54096
54097    _proto.videoTimestampOffset = function videoTimestampOffset(offset) {
54098      if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same
54099      this.videoTimestampOffset !== offset) {
54100        pushQueue({
54101          type: 'video',
54102          sourceUpdater: this,
54103          action: actions.timestampOffset(offset),
54104          name: 'timestampOffset'
54105        });
54106        this.videoTimestampOffset_ = offset;
54107      }
54108
54109      return this.videoTimestampOffset_;
54110    }
54111    /**
54112     * Add a function to the queue that will be called
54113     * when it is its turn to run in the audio queue.
54114     *
54115     * @param {Function} callback
54116     *        The callback to queue.
54117     */
54118    ;
54119
54120    _proto.audioQueueCallback = function audioQueueCallback(callback) {
54121      if (!this.audioBuffer) {
54122        return;
54123      }
54124
54125      pushQueue({
54126        type: 'audio',
54127        sourceUpdater: this,
54128        action: actions.callback(callback),
54129        name: 'callback'
54130      });
54131    }
54132    /**
54133     * Add a function to the queue that will be called
54134     * when it is its turn to run in the video queue.
54135     *
54136     * @param {Function} callback
54137     *        The callback to queue.
54138     */
54139    ;
54140
54141    _proto.videoQueueCallback = function videoQueueCallback(callback) {
54142      if (!this.videoBuffer) {
54143        return;
54144      }
54145
54146      pushQueue({
54147        type: 'video',
54148        sourceUpdater: this,
54149        action: actions.callback(callback),
54150        name: 'callback'
54151      });
54152    }
54153    /**
54154     * dispose of the source updater and the underlying sourceBuffer
54155     */
54156    ;
54157
54158    _proto.dispose = function dispose() {
54159      var _this4 = this;
54160
54161      this.trigger('dispose');
54162      bufferTypes.forEach(function (type) {
54163        _this4.abort(type);
54164
54165        if (_this4.canRemoveSourceBuffer()) {
54166          _this4.removeSourceBuffer(type);
54167        } else {
54168          _this4[type + "QueueCallback"](function () {
54169            return cleanupBuffer(type, _this4);
54170          });
54171        }
54172      });
54173      this.videoAppendQueued_ = false;
54174      this.delayedAudioAppendQueue_.length = 0;
54175
54176      if (this.sourceopenListener_) {
54177        this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
54178      }
54179
54180      this.off();
54181    };
54182
54183    return SourceUpdater;
54184  }(videojs$1.EventTarget);
54185
54186  var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
54187    return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
54188  };
54189
54190  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) {
54191    return _char2.charCodeAt(0);
54192  }));
54193  /**
54194   * An object that manages segment loading and appending.
54195   *
54196   * @class VTTSegmentLoader
54197   * @param {Object} options required and optional options
54198   * @extends videojs.EventTarget
54199   */
54200
54201  var VTTSegmentLoader = /*#__PURE__*/function (_SegmentLoader) {
54202    inheritsLoose(VTTSegmentLoader, _SegmentLoader);
54203
54204    function VTTSegmentLoader(settings, options) {
54205      var _this;
54206
54207      if (options === void 0) {
54208        options = {};
54209      }
54210
54211      _this = _SegmentLoader.call(this, settings, options) || this; // VTT can't handle partial data
54212
54213      _this.handlePartialData_ = false; // SegmentLoader requires a MediaSource be specified or it will throw an error;
54214      // however, VTTSegmentLoader has no need of a media source, so delete the reference
54215
54216      _this.mediaSource_ = null;
54217      _this.subtitlesTrack_ = null;
54218      _this.loaderType_ = 'subtitle';
54219      _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
54220      // the sync controller leads to improper behavior.
54221
54222      _this.shouldSaveSegmentTimingInfo_ = false;
54223      return _this;
54224    }
54225
54226    var _proto = VTTSegmentLoader.prototype;
54227
54228    _proto.createTransmuxer_ = function createTransmuxer_() {
54229      // don't need to transmux any subtitles
54230      return null;
54231    }
54232    /**
54233     * Indicates which time ranges are buffered
54234     *
54235     * @return {TimeRange}
54236     *         TimeRange object representing the current buffered ranges
54237     */
54238    ;
54239
54240    _proto.buffered_ = function buffered_() {
54241      if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
54242        return videojs$1.createTimeRanges();
54243      }
54244
54245      var cues = this.subtitlesTrack_.cues;
54246      var start = cues[0].startTime;
54247      var end = cues[cues.length - 1].startTime;
54248      return videojs$1.createTimeRanges([[start, end]]);
54249    }
54250    /**
54251     * Gets and sets init segment for the provided map
54252     *
54253     * @param {Object} map
54254     *        The map object representing the init segment to get or set
54255     * @param {boolean=} set
54256     *        If true, the init segment for the provided map should be saved
54257     * @return {Object}
54258     *         map object for desired init segment
54259     */
54260    ;
54261
54262    _proto.initSegmentForMap = function initSegmentForMap(map, set) {
54263      if (set === void 0) {
54264        set = false;
54265      }
54266
54267      if (!map) {
54268        return null;
54269      }
54270
54271      var id = initSegmentId(map);
54272      var storedMap = this.initSegments_[id];
54273
54274      if (set && !storedMap && map.bytes) {
54275        // append WebVTT line terminators to the media initialization segment if it exists
54276        // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
54277        // requires two or more WebVTT line terminators between the WebVTT header and the
54278        // rest of the file
54279        var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
54280        var combinedSegment = new Uint8Array(combinedByteLength);
54281        combinedSegment.set(map.bytes);
54282        combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
54283        this.initSegments_[id] = storedMap = {
54284          resolvedUri: map.resolvedUri,
54285          byterange: map.byterange,
54286          bytes: combinedSegment
54287        };
54288      }
54289
54290      return storedMap || map;
54291    }
54292    /**
54293     * Returns true if all configuration required for loading is present, otherwise false.
54294     *
54295     * @return {boolean} True if the all configuration is ready for loading
54296     * @private
54297     */
54298    ;
54299
54300    _proto.couldBeginLoading_ = function couldBeginLoading_() {
54301      return this.playlist_ && this.subtitlesTrack_ && !this.paused();
54302    }
54303    /**
54304     * Once all the starting parameters have been specified, begin
54305     * operation. This method should only be invoked from the INIT
54306     * state.
54307     *
54308     * @private
54309     */
54310    ;
54311
54312    _proto.init_ = function init_() {
54313      this.state = 'READY';
54314      this.resetEverything();
54315      return this.monitorBuffer_();
54316    }
54317    /**
54318     * Set a subtitle track on the segment loader to add subtitles to
54319     *
54320     * @param {TextTrack=} track
54321     *        The text track to add loaded subtitles to
54322     * @return {TextTrack}
54323     *        Returns the subtitles track
54324     */
54325    ;
54326
54327    _proto.track = function track(_track) {
54328      if (typeof _track === 'undefined') {
54329        return this.subtitlesTrack_;
54330      }
54331
54332      this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
54333      // buffering now
54334
54335      if (this.state === 'INIT' && this.couldBeginLoading_()) {
54336        this.init_();
54337      }
54338
54339      return this.subtitlesTrack_;
54340    }
54341    /**
54342     * Remove any data in the source buffer between start and end times
54343     *
54344     * @param {number} start - the start time of the region to remove from the buffer
54345     * @param {number} end - the end time of the region to remove from the buffer
54346     */
54347    ;
54348
54349    _proto.remove = function remove(start, end) {
54350      removeCuesFromTrack(start, end, this.subtitlesTrack_);
54351    }
54352    /**
54353     * fill the buffer with segements unless the sourceBuffers are
54354     * currently updating
54355     *
54356     * Note: this function should only ever be called by monitorBuffer_
54357     * and never directly
54358     *
54359     * @private
54360     */
54361    ;
54362
54363    _proto.fillBuffer_ = function fillBuffer_() {
54364      var _this2 = this;
54365
54366      if (!this.syncPoint_) {
54367        this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
54368      } // see if we need to begin loading immediately
54369
54370
54371      var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
54372      segmentInfo = this.skipEmptySegments_(segmentInfo);
54373
54374      if (!segmentInfo) {
54375        return;
54376      }
54377
54378      if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
54379        // We don't have the timestamp offset that we need to sync subtitles.
54380        // Rerun on a timestamp offset or user interaction.
54381        var checkTimestampOffset = function checkTimestampOffset() {
54382          _this2.state = 'READY';
54383
54384          if (!_this2.paused()) {
54385            // if not paused, queue a buffer check as soon as possible
54386            _this2.monitorBuffer_();
54387          }
54388        };
54389
54390        this.syncController_.one('timestampoffset', checkTimestampOffset);
54391        this.state = 'WAITING_ON_TIMELINE';
54392        return;
54393      }
54394
54395      this.loadSegment_(segmentInfo);
54396    }
54397    /**
54398     * Prevents the segment loader from requesting segments we know contain no subtitles
54399     * by walking forward until we find the next segment that we don't know 
54399whether it is
54400     * empty or not.
54401     *
54402     * @param {Object} segmentInfo
54403     *        a segment info object that describes the current segment
54404     * @return {Object}
54405     *         a segment info object that describes the current segment
54406     */
54407    ;
54408
54409    _proto.skipEmptySegments_ = function skipEmptySegments_(segmentInfo) {
54410      while (segmentInfo && segmentInfo.segment.empty) {
54411        segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
54412      }
54413
54414      return segmentInfo;
54415    };
54416
54417    _proto.stopForError = function stopForError(error) {
54418      this.error(error);
54419      this.state = 'READY';
54420      this.pause();
54421      this.trigger('error');
54422    }
54423    /**
54424     * append a decrypted segement to the SourceBuffer through a SourceUpdater
54425     *
54426     * @private
54427     */
54428    ;
54429
54430    _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
54431      var _this3 = this;
54432
54433      if (!this.subtitlesTrack_) {
54434        this.state = 'READY';
54435        return;
54436      }
54437
54438      this.saveTransferStats_(simpleSegment.stats); // the request was aborted
54439
54440      if (!this.pendingSegment_) {
54441        this.state = 'READY';
54442        this.mediaRequestsAborted += 1;
54443        return;
54444      }
54445
54446      if (error) {
54447        if (error.code === REQUEST_ERRORS.TIMEOUT) {
54448          this.handleTimeout_();
54449        }
54450
54451        if (error.code === REQUEST_ERRORS.ABORTED) {
54452          this.mediaRequestsAborted += 1;
54453        } else {
54454          this.mediaRequestsErrored += 1;
54455        }
54456
54457        this.stopForError(error);
54458        return;
54459      } // although the VTT segment loader bandwidth isn't really used, it's good to
54460      // maintain functionality between segment loaders
54461
54462
54463      this.saveBandwidthRelatedStats_(simpleSegment.stats);
54464      this.state = 'APPENDING'; // used for tests
54465
54466      this.trigger('appending');
54467      var segmentInfo = this.pendingSegment_;
54468      var segment = segmentInfo.segment;
54469
54470      if (segment.map) {
54471        segment.map.bytes = simpleSegment.map.bytes;
54472      }
54473
54474      segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
54475
54476      if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
54477        var loadHandler;
54478
54479        var errorHandler = function errorHandler() {
54480          _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
54481
54482          _this3.stopForError({
54483            message: 'Error loading vtt.js'
54484          });
54485
54486          return;
54487        };
54488
54489        loadHandler = function loadHandler() {
54490          _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler);
54491
54492          _this3.segmentRequestFinished_(error, simpleSegment, result);
54493        };
54494
54495        this.state = 'WAITING_ON_VTTJS';
54496        this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
54497        this.subtitlesTrack_.tech_.one('vttjserror', errorHandler);
54498        return;
54499      }
54500
54501      segment.requested = true;
54502
54503      try {
54504        this.parseVTTCues_(segmentInfo);
54505      } catch (e) {
54506        this.stopForError({
54507          message: e.message
54508        });
54509        return;
54510      }
54511
54512      this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
54513
54514      if (segmentInfo.cues.length) {
54515        segmentInfo.timingInfo = {
54516          start: segmentInfo.cues[0].startTime,
54517          end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
54518        };
54519      } else {
54520        segmentInfo.timingInfo = {
54521          start: segmentInfo.startOfSegment,
54522          end: segmentInfo.startOfSegment + segmentInfo.duration
54523        };
54524      }
54525
54526      if (segmentInfo.isSyncRequest) {
54527        this.trigger('syncinfoupdate');
54528        this.pendingSegment_ = null;
54529        this.state = 'READY';
54530        return;
54531      }
54532
54533      segmentInfo.byteLength = segmentInfo.bytes.byteLength;
54534      this.mediaSecondsLoaded += segment.duration;
54535      segmentInfo.cues.forEach(function (cue) {
54536        // remove any overlapping cues to prevent doubling
54537        _this3.remove(cue.startTime, cue.endTime);
54538
54539        _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window$1.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
54540      });
54541      this.handleAppendsDone_();
54542    };
54543
54544    _proto.handleData_ = function handleData_() {// noop as we shouldn't be getting video/audio data captions
54545      // that we do not support here.
54546    };
54547
54548    _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_() {} // noop
54549
54550    /**
54551     * Uses the WebVTT parser to parse the segment response
54552     *
54553     * @param {Object} segmentInfo
54554     *        a segment info object that describes the current segment
54555     * @private
54556     */
54557    ;
54558
54559    _proto.parseVTTCues_ = function parseVTTCues_(segmentInfo) {
54560      var decoder;
54561      var decodeBytesToString = false;
vendor: 4,261 bytes, lines 54562-54704
54562
54563      if (typeof window$1.TextDecoder === 'function') {
54564        decoder = new window$1.TextDecoder('utf8');
54565      } else {
54566        decoder = window$1.WebVTT.StringDecoder();
54567        decodeBytesToString = true;
54568      }
54569
54570      var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
54571      segmentInfo.cues = [];
54572      segmentInfo.timestampmap = {
54573        MPEGTS: 0,
54574        LOCAL: 0
54575      };
54576      parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
54577
54578      parser.ontimestampmap = function (map) {
54579        segmentInfo.timestampmap = map;
54580      };
54581
54582      parser.onparsingerror = function (error) {
54583        videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
54584      };
54585
54586      if (segmentInfo.segment.map) {
54587        var mapData = segmentInfo.segment.map.bytes;
54588
54589        if (decodeBytesToString) {
54590          mapData = uint8ToUtf8(mapData);
54591        }
54592
54593        parser.parse(mapData);
54594      }
54595
54596      var segmentData = segmentInfo.bytes;
54597
54598      if (decodeBytesToString) {
54599        segmentData = uint8ToUtf8(segmentData);
54600      }
54601
54602      parser.parse(segmentData);
54603      parser.flush();
54604    }
54605    /**
54606     * Updates the start and end times of any cues parsed by the WebVTT parser using
54607     * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
54608     * from the SyncController
54609     *
54610     * @param {Object} segmentInfo
54611     *        a segment info object that describes the current segment
54612     * @param {Object} mappingObj
54613     *        object containing a mapping from TS to media time
54614     * @param {Object} playlist
54615     *        the playlist object containing the segment
54616     * @private
54617     */
54618    ;
54619
54620    _proto.updateTimeMapping_ = function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
54621      var segment = segmentInfo.segment;
54622
54623      if (!mappingObj) {
54624        // If the sync controller does not have a mapping of TS to Media Time for the
54625        // timeline, then we don't have enough information to update the cue
54626        // start/end times
54627        return;
54628      }
54629
54630      if (!segmentInfo.cues.length) {
54631        // If there are no cues, we also do not have enough information to figure out
54632        // segment timing. Mark that the segment contains no cues so we don't re-request
54633        // an empty segment.
54634        segment.empty = true;
54635        return;
54636      }
54637
54638      var timestampmap = segmentInfo.timestampmap;
54639      var diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping;
54640      segmentInfo.cues.forEach(function (cue) {
54641        // First convert cue time to TS time using the timestamp-map provided within the vtt
54642        cue.startTime += diff;
54643        cue.endTime += diff;
54644      });
54645
54646      if (!playlist.syncInfo) {
54647        var firstStart = segmentInfo.cues[0].startTime;
54648        var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
54649        playlist.syncInfo = {
54650          mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
54651          time: Math.min(firstStart, lastStart - segment.duration)
54652        };
54653      }
54654    };
54655
54656    return VTTSegmentLoader;
54657  }(SegmentLoader);
54658  /**
54659   * @file ad-cue-tags.js
54660   */
54661
54662  /**
54663   * Searches for an ad cue that overlaps with the given mediaTime
54664   */
54665
54666
54667  var findAdCue = function findAdCue(track, mediaTime) {
54668    var cues = track.cues;
54669
54670    for (var i = 0; i < cues.length; i++) {
54671      var cue = cues[i];
54672
54673      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
54674        return cue;
54675      }
54676    }
54677
54678    return null;
54679  };
54680
54681  var updateAdCues = function updateAdCues(media, track, offset) {
54682    if (offset === void 0) {
54683      offset = 0;
54684    }
54685
54686    if (!media.segments) {
54687      return;
54688    }
54689
54690    var mediaTime = offset;
54691    var cue;
54692
54693    for (var i = 0; i < media.segments.length; i++) {
54694      var segment = media.segments[i];
54695
54696      if (!cue) {
54697        // Since the cues will span for at least the segment duration, adding a fudge
54698        // factor of half segment duration will prevent duplicate cues from being
54699        // created when timing info is not exact (e.g. cue start time initialized
54700        // at 10.006677, but next call mediaTime is 10.003332 )
54701        cue = findAdCue(track, mediaTime + segment.duration / 2);
54702      }
54703
54704      if (cue) {
vendor: 12,932 bytes, lines 54705-55066
54705        if ('cueIn' in segment) {
54706          // Found a CUE-IN so end the cue
54707          cue.endTime = mediaTime;
54708          cue.adEndTime = mediaTime;
54709          mediaTime += segment.duration;
54710          cue = null;
54711          continue;
54712        }
54713
54714        if (mediaTime < cue.endTime) {
54715          // Already processed this mediaTime for this cue
54716          mediaTime += segment.duration;
54717          continue;
54718        } // otherwise extend cue until a CUE-IN is found
54719
54720
54721        cue.endTime += segment.duration;
54722      } else {
54723        if ('cueOut' in segment) {
54724          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
54725          cue.adStartTime = mediaTime; // Assumes tag format to be
54726          // #EXT-X-CUE-OUT:30
54727
54728          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
54729          track.addCue(cue);
54730        }
54731
54732        if ('cueOutCont' in segment) {
54733          // Entered into the middle of an ad cue
54734          // Assumes tag formate to be
54735          // #EXT-X-CUE-OUT-CONT:10/30
54736          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat),
54737              adOffset = _segment$cueOutCont$s[0],
54738              adTotal = _segment$cueOutCont$s[1];
54739
54740          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
54741          cue.adStartTime = mediaTime - adOffset;
54742          cue.adEndTime = cue.adStartTime + adTotal;
54743          track.addCue(cue);
54744        }
54745      }
54746
54747      mediaTime += segment.duration;
54748    }
54749  };
54750
54751  var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
54752  //                the equivalence display-time 0 === segment-index 0
54753  {
54754    name: 'VOD',
54755    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
54756      if (duration !== Infinity) {
54757        var syncPoint = {
54758          time: 0,
54759          segmentIndex: 0
54760        };
54761        return syncPoint;
54762      }
54763
54764      return null;
54765    }
54766  }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
54767  {
54768    name: 'ProgramDateTime',
54769    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
54770      if (!syncController.datetimeToDisplayTime) {
54771        return null;
54772      }
54773
54774      var segments = playlist.segments || [];
54775      var syncPoint = null;
54776      var lastDistance = null;
54777      currentTime = currentTime || 0;
54778
54779      for (var i = 0; i < segments.length; i++) {
54780        var segment = segments[i];
54781
54782        if (segment.dateTimeObject) {
54783          var segmentTime = segment.dateTimeObject.getTime() / 1000;
54784          var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
54785          var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed
54786          // currentTime and can stop looking for better candidates
54787
54788          if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
54789            break;
54790          }
54791
54792          lastDistance = distance;
54793          syncPoint = {
54794            time: segmentStart,
54795            segmentIndex: i
54796          };
54797        }
54798      }
54799
54800      return syncPoint;
54801    }
54802  }, // Stategy "Segment": We have a known time mapping for a timeline and a
54803  //                    segment in the current timeline with timing data
54804  {
54805    name: 'Segment',
54806    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
54807      var segments = playlist.segments || [];
54808      var syncPoint = null;
54809      var lastDistance = null;
54810      currentTime = currentTime || 0;
54811
54812      for (var i = 0; i < segments.length; i++) {
54813        var segment = segments[i];
54814
54815        if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
54816          var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
54817          // currentTime and can stop looking for better candidates
54818
54819          if (lastDistance !== null && lastDistance < distance) {
54820            break;
54821          }
54822
54823          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
54824            lastDistance = distance;
54825            syncPoint = {
54826              time: segment.start,
54827              segmentIndex: i
54828            };
54829          }
54830        }
54831      }
54832
54833      return syncPoint;
54834    }
54835  }, // Stategy "Discontinuity": We have a discontinuity with a known
54836  //                          display-time
54837  {
54838    name: 'Discontinuity',
54839    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
54840      var syncPoint = null;
54841      currentTime = currentTime || 0;
54842
54843      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
54844        var lastDistance = null;
54845
54846        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
54847          var segmentIndex = playlist.discontinuityStarts[i];
54848          var discontinuity = playlist.discontinuitySequence + i + 1;
54849          var discontinuitySync = syncController.discontinuities[discontinuity];
54850
54851          if (discontinuitySync) {
54852            var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
54853            // currentTime and can stop looking for better candidates
54854
54855            if (lastDistance !== null && lastDistance < distance) {
54856              break;
54857            }
54858
54859            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
54860              lastDistance = distance;
54861              syncPoint = {
54862                time: discontinuitySync.time,
54863                segmentIndex: segmentIndex
54864              };
54865            }
54866          }
54867        }
54868      }
54869
54870      return syncPoint;
54871    }
54872  }, // Stategy "Playlist": We have a playlist with a known mapping of
54873  //                     segment index to display time
54874  {
54875    name: 'Playlist',
54876    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
54877      if (playlist.syncInfo) {
54878        var syncPoint = {
54879          time: playlist.syncInfo.time,
54880          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
54881        };
54882        return syncPoint;
54883      }
54884
54885      return null;
54886    }
54887  }];
54888
54889  var SyncController = /*#__PURE__*/function (_videojs$EventTarget) {
54890    inheritsLoose(SyncController, _videojs$EventTarget);
54891
54892    function SyncController(options) {
54893      var _this;
54894
54895      _this = _videojs$EventTarget.call(this) || this; // ...for synching across variants
54896
54897      _this.timelines = [];
54898      _this.discontinuities = [];
54899      _this.datetimeToDisplayTime = null;
54900      _this.logger_ = logger('SyncController');
54901      return _this;
54902    }
54903    /**
54904     * Find a sync-point for the playlist specified
54905     *
54906     * A sync-point is defined as a known mapping from display-time to
54907     * a segment-index in the current playlist.
54908     *
54909     * @param {Playlist} playlist
54910     *        The playlist that needs a sync-point
54911     * @param {number} duration
54912     *        Duration of the MediaSource (Infinite if playing a live source)
54913     * @param {number} currentTimeline
54914     *        The last timeline from which a segment was loaded
54915     * @return {Object}
54916     *          A sync-point object
54917     */
54918
54919
54920    var _proto = SyncController.prototype;
54921
54922    _proto.getSyncPoint = function getSyncPoint(playlist, duration, currentTimeline, currentTime) {
54923      var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime);
54924
54925      if (!syncPoints.length) {
54926        // Signal that we need to attempt to get a sync-point manually
54927        // by fetching a segment in the playlist and constructing
54928        // a sync-point from that information
54929        return null;
54930      } // Now find the sync-point that is closest to the currentTime because
54931      // that should result in the most accurate guess about which segment
54932      // to fetch
54933
54934
54935      return this.selectSyncPoint_(syncPoints, {
54936        key: 'time',
54937        value: currentTime
54938      });
54939    }
54940    /**
54941     * Calculate the amount of time that has expired off the playlist during playback
54942     *
54943     * @param {Playlist} playlist
54944     *        Playlist object to calculate expired from
54945     * @param {number} duration
54946     *        Duration of the MediaSource (Infinity if playling a live source)
54947     * @return {number|null}
54948     *          The amount of time that has expired off the playlist during playback. Null
54949     *          if no sync-points for the playlist can be found.
54950     */
54951    ;
54952
54953    _proto.getExpiredTime = function getExpiredTime(playlist, duration) {
54954      if (!playlist || !playlist.segments) {
54955        return null;
54956      }
54957
54958      var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
54959
54960      if (!syncPoints.length) {
54961        return null;
54962      }
54963
54964      var syncPoint = this.selectSyncPoint_(syncPoints, {
54965        key: 'segmentIndex',
54966        value: 0
54967      }); // If the sync-point is beyond the start of the playlist, we want to subtract the
54968      // duration from index 0 to syncPoint.segmentIndex instead of adding.
54969
54970      if (syncPoint.segmentIndex > 0) {
54971        syncPoint.time *= -1;
54972      }
54973
54974      return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
54975    }
54976    /**
54977     * Runs each sync-point strategy and returns a list of sync-points returned by the
54978     * strategies
54979     *
54980     * @private
54981     * @param {Playlist} playlist
54982     *        The playlist that needs a sync-point
54983     * @param {number} duration
54984     *        Duration of the MediaSource (Infinity if playing a live source)
54985     * @param {number} currentTimeline
54986     *        The last timeline from which a segment was loaded
54987     * @return {Array}
54988     *          A list of sync-point objects
54989     */
54990    ;
54991
54992    _proto.runStrategies_ = function runStrategies_(playlist, duration, currentTimeline, currentTime) {
54993      var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
54994
54995      for (var i = 0; i < syncPointStrategies.length; i++) {
54996        var strategy = syncPointStrategies[i];
54997        var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime);
54998
54999        if (syncPoint) {
55000          syncPoint.strategy = strategy.name;
55001          syncPoints.push({
55002            strategy: strategy.name,
55003            syncPoint: syncPoint
55004          });
55005        }
55006      }
55007
55008      return syncPoints;
55009    }
55010    /**
55011     * Selects the sync-point nearest the specified target
55012     *
55013     * @private
55014     * @param {Array} syncPoints
55015     *        List of sync-points to select from
55016     * @param {Object} target
55017     *        Object specifying the property and value we are targeting
55018     * @param {string} target.key
55019     *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
55020     * @param {number} target.value
55021     *        The value to target for the specified key.
55022     * @return {Object}
55023     *          The sync-point nearest the target
55024     */
55025    ;
55026
55027    _proto.selectSyncPoint_ = function selectSyncPoint_(syncPoints, target) {
55028      var bestSyncPoint = syncPoints[0].syncPoint;
55029      var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
55030      var bestStrategy = syncPoints[0].strategy;
55031
55032      for (var i = 1; i < syncPoints.length; i++) {
55033        var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
55034
55035        if (newDistance < bestDistance) {
55036          bestDistance = newDistance;
55037          bestSyncPoint = syncPoints[i].syncPoint;
55038          bestStrategy = syncPoints[i].strategy;
55039        }
55040      }
55041
55042      this.logger_("syncPoint for [" + target.key + ": " + target.value + "] chosen with strategy" + (" [" + bestStrategy + "]: [time:" + bestSyncPoint.time + ",") + (" segmentIndex:" + bestSyncPoint.segmentIndex + "]"));
55043      return bestSyncPoint;
55044    }
55045    /**
55046     * Save any meta-data present on the segments when segments leave
55047     * the live window to the playlist to allow for synchronization at the
55048     * playlist level later.
55049     *
55050     * @param {Playlist} oldPlaylist - The previous active playlist
55051     * @param {Playlist} newPlaylist - The updated and most current playlist
55052     */
55053    ;
55054
55055    _proto.saveExpiredSegmentInfo = function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
55056      var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
55057      // save that information as a possible sync-point reference in future
55058
55059      for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
55060        var lastRemovedSegment = oldPlaylist.segments[i];
55061
55062        if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
55063          newPlaylist.syncInfo = {
55064            mediaSequence: oldPlaylist.mediaSequence + i,
55065            time: lastRemovedSegment.start
55066          };
55067          this.logger_("playlist refresh sync: [time:" + newPlaylist.syncInfo.time + "," + (" mediaSequence: " + newPlaylist.syncInfo.mediaSequence + "]"));
55068          this.trigger('syncinfoupdate');
55069          break;
55070        }
55071      }
55072    }
55073    /**
55074     * Save the mapping from playlist's ProgramDateTime to display. This should
55075     * only ever happen once at the start of playback.
55076     *
55077     * @param {Playlist} playlist - The currently active playlist
55078     */
55079    ;
55080
55081    _proto.setDateTimeMapping = function setDateTimeMapping(playlist) {
55082      if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
55083        var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
55084        this.datetimeToDisplayTime = -playlistTimestamp;
55085      }
55086    }
55087    /**
55088     * Calculates and saves timeline mappings, playlist sync info, and segment timing values
55089     * based on the latest timing information.
55090     *
55091     * @param {Object} options
55092     *        Options object
55093     * @param {SegmentInfo} options.segmentInfo
55094     *        The current active request information
55095     * @param {boolean} options.shouldSaveTimelineMapping
55096     *        If there's a timeline change, determines if the timeline mapping should be
55097     *        saved in timelines.
55098     */
55099    ;
55100
55101    _proto.saveSegmentTimingInfo = function saveSegmentTimingInfo(_ref) {
55102      var segmentInfo = _ref.segmentInfo,
55103          shouldSaveTimelineMapping = _ref.shouldSaveTimelineMapping;
55104      var didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
55105
55106      if (didCalculateSegmentTimeMapping) {
55107        this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
55108        // now with segment timing information
55109
55110        if (!segmentInfo.playlist.syncInfo) {
55111          segmentInfo.playlist.syncInfo = {
55112            mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
55113            time: segmentInfo.segment.start
55114          };
55115        }
55116      }
55117    };
55118
55119    _proto.timestampOffsetForTimeline = function timestampOffsetForTimeline(timeline) {
55120      if (typeof this.timelines[timeline] === 'undefined') {
55121        return null;
55122      }
55123
55124      return this.timelines[timeline].time;
55125    };
55126
55127    _proto.mappingForTimeline = function mappingForTimeline(timeline) {
55128      if (typeof this.timelines[timeline] === 'undefined') {
55129        return null;
55130      }
55131
55132      return this.timelines[timeline].mapping;
55133    }
55134    /**
55135     * Use the "media time" for a segment to generate a mapping to "display time" and
55136     * save that display time to the segment.
55137     *
55138     * @private
55139     * @param {SegmentInfo} segmentInfo
55140     *        The current active request information
55141     * @param {Object} timingInfo
55142     *        The start and end time of the current segment in "media time"
55143     * @param {boolean} shouldSaveTimelineMapping
55144     *        If there's a timeline change, determines if the timeline mapping should be
55145     *        saved in timelines.
55146     * @return {boolean}
55147     *          Returns false if segment time mapping could not be calculated
55148     */
55149    ;
55150
55151    _proto.calculateSegmentTimeMapping_ = function calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
55152      var segment = segmentInfo.segment;
55153      var mappingObj = this.timelines[segmentInfo.timeline];
55154
55155      if (segmentInfo.timestampOffset !== null) {
55156        mappingObj = {
55157          time: segmentInfo.startOfSegment,
55158          mapping: segmentInfo.startOfSegment - timingInfo.start
55159        };
55160
55161        if (shouldSaveTimelineMapping) {
55162          this.timelines[segmentInfo.timeline] = mappingObj;
55163          this.trigger('timestampoffset');
55164          this.logger_("time mapping for timeline " + segmentInfo.timeline + ": " + ("[time: " + mappingObj.time + "] [mapping: " + mappingObj.mapping + "]"));
55165        }
55166
55167        segment.start = segmentInfo.startOfSegment;
55168        segment.end = timingInfo.end + mappingObj.mapping;
55169      } else if (mappingObj) {
55170        segment.start = timingInfo.start + mappingObj.mapping;
55171        segment.end = timingInfo.end + mappingObj.mapping;
55172      } else {
55173        return false;
vendor: 6,954 bytes, lines 55174-55384
55174      }
55175
55176      return true;
55177    }
55178    /**
55179     * Each time we have discontinuity in the playlist, attempt to calculate the location
55180     * in display of the start of the discontinuity and save that. We also save an accuracy
55181     * value so that we save values with the most accuracy (closest to 0.)
55182     *
55183     * @private
55184     * @param {SegmentInfo} segmentInfo - The current active request information
55185     */
55186    ;
55187
55188    _proto.saveDiscontinuitySyncInfo_ = function saveDiscontinuitySyncInfo_(segmentInfo) {
55189      var playlist = segmentInfo.playlist;
55190      var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
55191      // the start of the range and it's accuracy is 0 (greater accuracy values
55192      // mean more approximation)
55193
55194      if (segment.discontinuity) {
55195        this.discontinuities[segment.timeline] = {
55196          time: segment.start,
55197          accuracy: 0
55198        };
55199      } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
55200        // Search for future discontinuities that we can provide better timing
55201        // information for and save that information for sync purposes
55202        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
55203          var segmentIndex = playlist.discontinuityStarts[i];
55204          var discontinuity = playlist.discontinuitySequence + i + 1;
55205          var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
55206          var accuracy = Math.abs(mediaIndexDiff);
55207
55208          if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
55209            var time = void 0;
55210
55211            if (mediaIndexDiff < 0) {
55212              time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
55213            } else {
55214              time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
55215            }
55216
55217            this.discontinuities[discontinuity] = {
55218              time: time,
55219              accuracy: accuracy
55220            };
55221          }
55222        }
55223      }
55224    };
55225
55226    _proto.dispose = function dispose() {
55227      this.trigger('dispose');
55228      this.off();
55229    };
55230
55231    return SyncController;
55232  }(videojs$1.EventTarget);
55233  /**
55234   * The TimelineChangeController acts as a source for segment loaders to listen for and
55235   * keep track of latest and pending timeline changes. This is useful to ensure proper
55236   * sync, as each loader may need to make a consideration for what timeline the other
55237   * loader is on before making changes which could impact the other loader's media.
55238   *
55239   * @class TimelineChangeController
55240   * @extends videojs.EventTarget
55241   */
55242
55243
55244  var TimelineChangeController = /*#__PURE__*/function (_videojs$EventTarget) {
55245    inheritsLoose(TimelineChangeController, _videojs$EventTarget);
55246
55247    function TimelineChangeController() {
55248      var _this;
55249
55250      _this = _videojs$EventTarget.call(this) || this;
55251      _this.pendingTimelineChanges_ = {};
55252      _this.lastTimelineChanges_ = {};
55253      return _this;
55254    }
55255
55256    var _proto = TimelineChangeController.prototype;
55257
55258    _proto.clearPendingTimelineChange = function clearPendingTimelineChange(type) {
55259      this.pendingTimelineChanges_[type] = null;
55260      this.trigger('pendingtimelinechange');
55261    };
55262
55263    _proto.pendingTimelineChange = function pendingTimelineChange(_ref) {
55264      var type = _ref.type,
55265          from = _ref.from,
55266          to = _ref.to;
55267
55268      if (typeof from === 'number' && typeof to === 'number') {
55269        this.pendingTimelineChanges_[type] = {
55270          type: type,
55271          from: from,
55272          to: to
55273        };
55274        this.trigger('pendingtimelinechange');
55275      }
55276
55277      return this.pendingTimelineChanges_[type];
55278    };
55279
55280    _proto.lastTimelineChange = function lastTimelineChange(_ref2) {
55281      var type = _ref2.type,
55282          from = _ref2.from,
55283          to = _ref2.to;
55284
55285      if (typeof from === 'number' && typeof to === 'number') {
55286        this.lastTimelineChanges_[type] = {
55287          type: type,
55288          from: from,
55289          to: to
55290        };
55291        delete this.pendingTimelineChanges_[type];
55292        this.trigger('timelinechange');
55293      }
55294
55295      return this.lastTimelineChanges_[type];
55296    };
55297
55298    _proto.dispose = function dispose() {
55299      this.trigger('dispose');
55300      this.pendingTimelineChanges_ = {};
55301      this.lastTimelineChanges_ = {};
55302      this.off();
55303    };
55304
55305    return TimelineChangeController;
55306  }(videojs$1.EventTarget);
55307
55308  var Decrypter = new shimWorker("./decrypter-worker.worker.js", function (window, document) {
55309    var self = this;
55310    /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */
55311
55312    var decrypterWorker = function () {
55313      function _defineProperties(target, props) {
55314        for (var i = 0; i < props.length; i++) {
55315          var descriptor = props[i];
55316          descriptor.enumerable = descriptor.enumerable || false;
55317          descriptor.configurable = true;
55318          if ("value" in descriptor) descriptor.writable = true;
55319          Object.defineProperty(target, descriptor.key, descriptor);
55320        }
55321      }
55322
55323      function _createClass(Constructor, protoProps, staticProps) {
55324        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
55325        if (staticProps) _defineProperties(Constructor, staticProps);
55326        return Constructor;
55327      }
55328
55329      var createClass = _createClass;
55330
55331      function _inheritsLoose(subClass, superClass) {
55332        subClass.prototype = Object.create(superClass.prototype);
55333        subClass.prototype.constructor = subClass;
55334        subClass.__proto__ = superClass;
55335      }
55336
55337      var inheritsLoose = _inheritsLoose;
55338      /*! @name @videojs/vhs-utils @version 2.2.1 @license MIT */
55339
55340      /**
55341       * @file stream.js
55342       */
55343
55344      /**
55345       * A lightweight readable stream implemention that handles event dispatching.
55346       *
55347       * @class Stream
55348       */
55349
55350      var Stream = /*#__PURE__*/function () {
55351        function Stream() {
55352          this.listeners = {};
55353        }
55354        /**
55355         * Add a listener for a specified event type.
55356         *
55357         * @param {string} type the event name
55358         * @param {Function} listener the callback to be invoked when an event of
55359         * the specified type occurs
55360         */
55361
55362
55363        var _proto = Stream.prototype;
55364
55365        _proto.on = function on(type, listener) {
55366          if (!this.listeners[type]) {
55367            this.listeners[type] = [];
55368          }
55369
55370          this.listeners[type].push(listener);
55371        }
55372        /**
55373         * Remove a listener for a specified event type.
55374         *
55375         * @param {string} type the event name
55376         * @param {Function} listener  a function previously registered for this
55377         * type of event through `on`
55378         * @return {boolean} if we could turn it off or not
55379         */
55380        ;
55381
55382        _proto.off = function off(type, listener) {
55383          if (!this.listeners[type]) {
55384            return false;
vendor: 4,383 bytes, lines 55385-55503
55385          }
55386
55387          var index = this.listeners[type].indexOf(listener); // TODO: which is better?
55388          // In Video.js we slice listener functions
55389          // on trigger so that it does not mess up the order
55390          // while we loop through.
55391          //
55392          // Here we slice on off so that the loop in trigger
55393          // can continue using it's old reference to loop without
55394          // messing up the order.
55395
55396          this.listeners[type] = this.listeners[type].slice(0);
55397          this.listeners[type].splice(index, 1);
55398          return index > -1;
55399        }
55400        /**
55401         * Trigger an event of the specified type on this stream. Any additional
55402         * arguments to this function are passed as parameters to event listeners.
55403         *
55404         * @param {string} type the event name
55405         */
55406        ;
55407
55408        _proto.trigger = function trigger(type) {
55409          var callbacks = this.listeners[type];
55410
55411          if (!callbacks) {
55412            return;
55413          } // Slicing the arguments on every invocation of this method
55414          // can add a significant amount of overhead. Avoid the
55415          // intermediate object creation for the common case of a
55416          // single callback argument
55417
55418
55419          if (arguments.length === 2) {
55420            var length = callbacks.length;
55421
55422            for (var i = 0; i < length; ++i) {
55423              callbacks[i].call(this, arguments[1]);
55424            }
55425          } else {
55426            var args = Array.prototype.slice.call(arguments, 1);
55427            var _length = callbacks.length;
55428
55429            for (var _i = 0; _i < _length; ++_i) {
55430              callbacks[_i].apply(this, args);
55431            }
55432          }
55433        }
55434        /**
55435         * Destroys the stream and cleans up.
55436         */
55437        ;
55438
55439        _proto.dispose = function dispose() {
55440          this.listeners = {};
55441        }
55442        /**
55443         * Forwards all `data` events on this stream to the destination stream. The
55444         * destination stream should provide a method `push` to receive the data
55445         * events as they arrive.
55446         *
55447         * @param {Stream} destination the stream that will receive all `data` events
55448         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
55449         */
55450        ;
55451
55452        _proto.pipe = function pipe(destination) {
55453          this.on('data', function (data) {
55454            destination.push(data);
55455          });
55456        };
55457
55458        return Stream;
55459      }();
55460
55461      var stream = Stream;
55462      /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
55463
55464      /**
55465       * Returns the subarray of a Uint8Array without PKCS#7 padding.
55466       *
55467       * @param padded {Uint8Array} unencrypted bytes that have been padded
55468       * @return {Uint8Array} the unpadded bytes
55469       * @see http://tools.ietf.org/html/rfc5652
55470       */
55471
55472      function unpad(padded) {
55473        return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
55474      }
55475      /*! @name aes-decrypter @version 3.1.0 @license Apache-2.0 */
55476
55477      /**
55478       * @file aes.js
55479       *
55480       * This file contains an adaptation of the AES decryption algorithm
55481       * from the Standford Javascript Cryptography Library. That work is
55482       * covered by the following copyright and permissions notice:
55483       *
55484       * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
55485       * All rights reserved.
55486       *
55487       * Redistribution and use in source and binary forms, with or without
55488       * modification, are permitted provided that the following conditions are
55489       * met:
55490       *
55491       * 1. Redistributions of source code must retain the above copyright
55492       *    notice, this list of conditions and the following disclaimer.
55493       *
55494       * 2. Redistributions in binary form must reproduce the above
55495       *    copyright notice, this list of conditions and the following
55496       *    disclaimer in the documentation and/or other materials provided
55497       *    with the distribution.
55498       *
55499       * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
55500       * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
55501       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55502       * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
55503       * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
vendor: 45,948 bytes, lines 55504-56767
55504       * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
55505       * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
55506       * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
55507       * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
55508       * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
55509       * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55510       *
55511       * The views and conclusions contained in the software and documentation
55512       * are those of the authors and should not be interpreted as representing
55513       * official policies, either expressed or implied, of the authors.
55514       */
55515
55516      /**
55517       * Expand the S-box tables.
55518       *
55519       * @private
55520       */
55521
55522
55523      var precompute = function precompute() {
55524        var tables = [[[], [], [], [], []], [[], [], [], [], []]];
55525        var encTable = tables[0];
55526        var decTable = tables[1];
55527        var sbox = encTable[4];
55528        var sboxInv = decTable[4];
55529        var i;
55530        var x;
55531        var xInv;
55532        var d = [];
55533        var th = [];
55534        var x2;
55535        var x4;
55536        var x8;
55537        var s;
55538        var tEnc;
55539        var tDec; // Compute double and third tables
55540
55541        for (i = 0; i < 256; i++) {
55542          th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
55543        }
55544
55545        for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
55546          // Compute sbox
55547          s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
55548          s = s >> 8 ^ s & 255 ^ 99;
55549          sbox[x] = s;
55550          sboxInv[s] = x; // Compute MixColumns
55551
55552          x8 = d[x4 = d[x2 = d[x]]];
55553          tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
55554          tEnc = d[s] * 0x101 ^ s * 0x1010100;
55555
55556          for (i = 0; i < 4; i++) {
55557            encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
55558            decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
55559          }
55560        } // Compactify. Considerable speedup on Firefox.
55561
55562
55563        for (i = 0; i < 5; i++) {
55564          encTable[i] = encTable[i].slice(0);
55565          decTable[i] = decTable[i].slice(0);
55566        }
55567
55568        return tables;
55569      };
55570
55571      var aesTables = null;
55572      /**
55573       * Schedule out an AES key for both encryption and decryption. This
55574       * is a low-level class. Use a cipher mode to do bulk encryption.
55575       *
55576       * @class AES
55577       * @param key {Array} The key as an array of 4, 6 or 8 words.
55578       */
55579
55580      var AES = /*#__PURE__*/function () {
55581        function AES(key) {
55582          /**
55583          * The expanded S-box and inverse S-box tables. These will be computed
55584          * on the client so that we don't have to send them down the wire.
55585          *
55586          * There are two tables, _tables[0] is for encryption and
55587          * _tables[1] is for decryption.
55588          *
55589          * The first 4 sub-tables are the expanded S-box with MixColumns. The
55590          * last (_tables[01][4]) is the S-box itself.
55591          *
55592          * @private
55593          */
55594          // if we have yet to precompute the S-box tables
55595          // do so now
55596          if (!aesTables) {
55597            aesTables = precompute();
55598          } // then make a copy of that object for use
55599
55600
55601          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()]];
55602          var i;
55603          var j;
55604          var tmp;
55605          var sbox = this._tables[0][4];
55606          var decTable = this._tables[1];
55607          var keyLen = key.length;
55608          var rcon = 1;
55609
55610          if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
55611            throw new Error('Invalid aes key size');
55612          }
55613
55614          var encKey = key.slice(0);
55615          var decKey = [];
55616          this._key = [encKey, decKey]; // schedule encryption keys
55617
55618          for (i = keyLen; i < 4 * keyLen + 28; i++) {
55619            tmp = encKey[i - 1]; // apply sbox
55620
55621            if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
55622              tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
55623
55624              if (i % keyLen === 0) {
55625                tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
55626                rcon = rcon << 1 ^ (rcon >> 7) * 283;
55627              }
55628            }
55629
55630            encKey[i] = encKey[i - keyLen] ^ tmp;
55631          } // schedule decryption keys
55632
55633
55634          for (j = 0; i; j++, i--) {
55635            tmp = encKey[j & 3 ? i : i - 4];
55636
55637            if (i <= 4 || j < 4) {
55638              decKey[j] = tmp;
55639            } else {
55640              decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
55641            }
55642          }
55643        }
55644        /**
55645         * Decrypt 16 bytes, specified as four 32-bit words.
55646         *
55647         * @param {number} encrypted0 the first word to decrypt
55648         * @param {number} encrypted1 the second word to decrypt
55649         * @param {number} encrypted2 the third word to decrypt
55650         * @param {number} encrypted3 the fourth word to decrypt
55651         * @param {Int32Array} out the array to write the decrypted words
55652         * into
55653         * @param {number} offset the offset into the output array to start
55654         * writing results
55655         * @return {Array} The plaintext.
55656         */
55657
55658
55659        var _proto = AES.prototype;
55660
55661        _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
55662          var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
55663
55664          var a = encrypted0 ^ key[0];
55665          var b = encrypted3 ^ key[1];
55666          var c = encrypted2 ^ key[2];
55667          var d = encrypted1 ^ key[3];
55668          var a2;
55669          var b2;
55670          var c2; // key.length === 2 ?
55671
55672          var nInnerRounds = key.length / 4 - 2;
55673          var i;
55674          var kIndex = 4;
55675          var table = this._tables[1]; // load up the tables
55676
55677          var table0 = table[0];
55678          var table1 = table[1];
55679          var table2 = table[2];
55680          var table3 = table[3];
55681          var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
55682
55683          for (i = 0; i < nInnerRounds; i++) {
55684            a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
55685            b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
55686            c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
55687            d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
55688            kIndex += 4;
55689            a = a2;
55690            b = b2;
55691            c = c2;
55692          } // Last round.
55693
55694
55695          for (i = 0; i < 4; i++) {
55696            out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
55697            a2 = a;
55698            a = b;
55699            b = c;
55700            c = d;
55701            d = a2;
55702          }
55703        };
55704
55705        return AES;
55706      }();
55707      /**
55708       * A wrapper around the Stream class to use setTimeout
55709       * and run stream "jobs" Asynchronously
55710       *
55711       * @class AsyncStream
55712       * @extends Stream
55713       */
55714
55715
55716      var AsyncStream = /*#__PURE__*/function (_Stream) {
55717        inheritsLoose(AsyncStream, _Stream);
55718
55719        function AsyncStream() {
55720          var _this;
55721
55722          _this = _Stream.call(this, stream) || this;
55723          _this.jobs = [];
55724          _this.delay = 1;
55725          _this.timeout_ = null;
55726          return _this;
55727        }
55728        /**
55729         * process an async job
55730         *
55731         * @private
55732         */
55733
55734
55735        var _proto = AsyncStream.prototype;
55736
55737        _proto.processJob_ = function processJob_() {
55738          this.jobs.shift()();
55739
55740          if (this.jobs.length) {
55741            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
55742          } else {
55743            this.timeout_ = null;
55744          }
55745        }
55746        /**
55747         * push a job into the stream
55748         *
55749         * @param {Function} job the job to push into the stream
55750         */
55751        ;
55752
55753        _proto.push = function push(job) {
55754          this.jobs.push(job);
55755
55756          if (!this.timeout_) {
55757            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
55758          }
55759        };
55760
55761        return AsyncStream;
55762      }(stream);
55763      /**
55764       * Convert network-order (big-endian) bytes into their little-endian
55765       * representation.
55766       */
55767
55768
55769      var ntoh = function ntoh(word) {
55770        return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
55771      };
55772      /**
55773       * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
55774       *
55775       * @param {Uint8Array} encrypted the encrypted bytes
55776       * @param {Uint32Array} key the bytes of the decryption key
55777       * @param {Uint32Array} initVector the initialization vector (IV) to
55778       * use for the first round of CBC.
55779       * @return {Uint8Array} the decrypted bytes
55780       *
55781       * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
55782       * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
55783       * @see https://tools.ietf.org/html/rfc2315
55784       */
55785
55786
55787      var decrypt = function decrypt(encrypted, key, initVector) {
55788        // word-level access to the encrypted bytes
55789        var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
55790        var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
55791
55792        var decrypted = new Uint8Array(encrypted.byteLength);
55793        var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
55794        // decrypted data
55795
55796        var init0;
55797        var init1;
55798        var init2;
55799        var init3;
55800        var encrypted0;
55801        var encrypted1;
55802        var encrypted2;
55803        var encrypted3; // iteration variable
55804
55805        var wordIx; // pull out the words of the IV to ensure we don't modify the
55806        // passed-in reference and easier access
55807
55808        init0 = initVector[0];
55809        init1 = initVector[1];
55810        init2 = initVector[2];
55811        init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
55812        // to each decrypted block
55813
55814        for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
55815          // convert big-endian (network order) words into little-endian
55816          // (javascript order)
55817          encrypted0 = ntoh(encrypted32[wordIx]);
55818          encrypted1 = ntoh(encrypted32[wordIx + 1]);
55819          encrypted2 = ntoh(encrypted32[wordIx + 2]);
55820          encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
55821
55822          decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
55823          // plaintext
55824
55825          decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
55826          decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
55827          decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
55828          decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
55829
55830          init0 = encrypted0;
55831          init1 = encrypted1;
55832          init2 = encrypted2;
55833          init3 = encrypted3;
55834        }
55835
55836        return decrypted;
55837      };
55838      /**
55839       * The `Decrypter` class that manages decryption of AES
55840       * data through `AsyncStream` objects and the `decrypt`
55841       * function
55842       *
55843       * @param {Uint8Array} encrypted the encrypted bytes
55844       * @param {Uint32Array} key the bytes of the decryption key
55845       * @param {Uint32Array} initVector the initialization vector (IV) to
55846       * @param {Function} done the function to run when done
55847       * @class Decrypter
55848       */
55849
55850
55851      var Decrypter = /*#__PURE__*/function () {
55852        function Decrypter(encrypted, key, initVector, done) {
55853          var step = Decrypter.STEP;
55854          var encrypted32 = new Int32Array(encrypted.buffer);
55855          var decrypted = new Uint8Array(encrypted.byteLength);
55856          var i = 0;
55857          this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
55858
55859          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
55860
55861          for (i = step; i < encrypted32.length; i += step) {
55862            initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
55863            this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
55864          } // invoke the done() callback when everything is finished
55865
55866
55867          this.asyncStream_.push(function () {
55868            // remove pkcs#7 padding from the decrypted bytes
55869            done(null, unpad(decrypted));
55870          });
55871        }
55872        /**
55873         * a getter for step the maximum number of bytes to process at one time
55874         *
55875         * @return {number} the value of step 32000
55876         */
55877
55878
55879        var _proto = Decrypter.prototype;
55880        /**
55881         * @private
55882         */
55883
55884        _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
55885          return function () {
55886            var bytes = decrypt(encrypted, key, initVector);
55887            decrypted.set(bytes, encrypted.byteOffset);
55888          };
55889        };
55890
55891        createClass(Decrypter, null, [{
55892          key: "STEP",
55893          get: function get() {
55894            // 4 * 8000;
55895            return 32000;
55896          }
55897        }]);
55898        return Decrypter;
55899      }();
55900      /**
55901       * @file bin-utils.js
55902       */
55903
55904      /**
55905       * Creates an object for sending to a web worker modifying properties that are TypedArrays
55906       * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
55907       *
55908       * @param {Object} message
55909       *        Object of properties and values to send to the web worker
55910       * @return {Object}
55911       *         Modified message with TypedArray values expanded
55912       * @function createTransferableMessage
55913       */
55914
55915
55916      var createTransferableMessage = function createTransferableMessage(message) {
55917        var transferable = {};
55918        Object.keys(message).forEach(function (key) {
55919          var value = message[key];
55920
55921          if (ArrayBuffer.isView(value)) {
55922            transferable[key] = {
55923              bytes: value.buffer,
55924              byteOffset: value.byteOffset,
55925              byteLength: value.byteLength
55926            };
55927          } else {
55928            transferable[key] = value;
55929          }
55930        });
55931        return transferable;
55932      };
55933      /* global self */
55934
55935      /**
55936       * Our web worker interface so that things can talk to aes-decrypter
55937       * that will be running in a web worker. the scope is passed to this by
55938       * webworkify.
55939       *
55940       * @param {Object} self
55941       *        the scope for the web worker
55942       */
55943
55944
55945      var DecrypterWorker = function DecrypterWorker(self) {
55946        self.onmessage = function (event) {
55947          var data = event.data;
55948          var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
55949          var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
55950          var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
55951          /* eslint-disable no-new, handle-callback-err */
55952
55953          new Decrypter(encrypted, key, iv, function (err, bytes) {
55954            self.postMessage(createTransferableMessage({
55955              source: data.source,
55956              decrypted: bytes
55957            }), [bytes.buffer]);
55958          });
55959          /* eslint-enable */
55960        };
55961      };
55962
55963      var decrypterWorker = new DecrypterWorker(self);
55964      return decrypterWorker;
55965    }();
55966  });
55967  /**
55968   * Convert the properties of an HLS track into an audioTrackKind.
55969   *
55970   * @private
55971   */
55972
55973  var audioTrackKind_ = function audioTrackKind_(properties) {
55974    var kind = properties["default"] ? 'main' : 'alternative';
55975
55976    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
55977      kind = 'main-desc';
55978    }
55979
55980    return kind;
55981  };
55982  /**
55983   * Pause provided segment loader and playlist loader if active
55984   *
55985   * @param {SegmentLoader} segmentLoader
55986   *        SegmentLoader to pause
55987   * @param {Object} mediaType
55988   *        Active media type
55989   * @function stopLoaders
55990   */
55991
55992
55993  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
55994    segmentLoader.abort();
55995    segmentLoader.pause();
55996
55997    if (mediaType && mediaType.activePlaylistLoader) {
55998      mediaType.activePlaylistLoader.pause();
55999      mediaType.activePlaylistLoader = null;
56000    }
56001  };
56002  /**
56003   * Start loading provided segment loader and playlist loader
56004   *
56005   * @param {PlaylistLoader} playlistLoader
56006   *        PlaylistLoader to start loading
56007   * @param {Object} mediaType
56008   *        Active media type
56009   * @function startLoaders
56010   */
56011
56012
56013  var startLoaders = function startLoaders(playlistLoader, mediaType) {
56014    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
56015    // playlist loader
56016    mediaType.activePlaylistLoader = playlistLoader;
56017    playlistLoader.load();
56018  };
56019  /**
56020   * Returns a function to be called when the media group changes. It performs a
56021   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
56022   * change of group is merely a rendition switch of the same content at another encoding,
56023   * rather than a change of content, such as switching audio from English to Spanish.
56024   *
56025   * @param {string} type
56026   *        MediaGroup type
56027   * @param {Object} settings
56028   *        Object containing required information for media groups
56029   * @return {Function}
56030   *         Handler for a non-destructive resync of SegmentLoader when the active media
56031   *         group changes.
56032   * @function onGroupChanged
56033   */
56034
56035
56036  var onGroupChanged = function onGroupChanged(type, settings) {
56037    return function () {
56038      var _settings$segmentLoad = settings.segmentLoaders,
56039          segmentLoader = _settings$segmentLoad[type],
56040          mainSegmentLoader = _settings$segmentLoad.main,
56041          mediaType = settings.mediaTypes[type];
56042      var activeTrack = mediaType.activeTrack();
56043      var activeGroup = mediaType.activeGroup(activeTrack);
56044      var previousActiveLoader = mediaType.activePlaylistLoader;
56045      stopLoaders(segmentLoader, mediaType);
56046
56047      if (!activeGroup) {
56048        // there is no group active
56049        return;
56050      }
56051
56052      if (!activeGroup.playlistLoader) {
56053        if (previousActiveLoader) {
56054          // The previous group had a playlist loader but the new active group does not
56055          // this means we are switching from demuxed to muxed audio. In this case we want to
56056          // do a destructive reset of the main segment loader and not restart the audio
56057          // loaders.
56058          mainSegmentLoader.resetEverything();
56059        }
56060
56061        return;
56062      } // Non-destructive resync
56063
56064
56065      segmentLoader.resyncLoader();
56066      startLoaders(activeGroup.playlistLoader, mediaType);
56067    };
56068  };
56069
56070  var onGroupChanging = function onGroupChanging(type, settings) {
56071    return function () {
56072      var segmentLoader = settings.segmentLoaders[type];
56073      segmentLoader.abort();
56074      segmentLoader.pause();
56075    };
56076  };
56077  /**
56078   * Returns a function to be called when the media track changes. It performs a
56079   * destructive reset of the SegmentLoader to ensure we start loading as close to
56080   * currentTime as possible.
56081   *
56082   * @param {string} type
56083   *        MediaGroup type
56084   * @param {Object} settings
56085   *        Object containing required information for media groups
56086   * @return {Function}
56087   *         Handler for a destructive reset of SegmentLoader when the active media
56088   *         track changes.
56089   * @function onTrackChanged
56090   */
56091
56092
56093  var onTrackChanged = function onTrackChanged(type, settings) {
56094    return function () {
56095      var _settings$segmentLoad2 = settings.segmentLoaders,
56096          segmentLoader = _settings$segmentLoad2[type],
56097          mainSegmentLoader = _settings$segmentLoad2.main,
56098          mediaType = settings.mediaTypes[type];
56099      var activeTrack = mediaType.activeTrack();
56100      var activeGroup = mediaType.activeGroup(activeTrack);
56101      var previousActiveLoader = mediaType.activePlaylistLoader;
56102      stopLoaders(segmentLoader, mediaType);
56103
56104      if (!activeGroup) {
56105        // there is no group active so we do not want to restart loaders
56106        return;
56107      }
56108
56109      if (type === 'AUDIO') {
56110        if (!activeGroup.playlistLoader) {
56111          // when switching from demuxed audio/video to muxed audio/video (noted by no
56112          // playlist loader for the audio group), we want to do a destructive reset of the
56113          // main segment loader and not restart the audio loaders
56114          mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
56115          // it should be stopped
56116
56117          mainSegmentLoader.resetEverything();
56118          return;
56119        } // although the segment loader is an audio segment loader, call the setAudio
56120        // function to ensure it is prepared to re-append the init segment (or handle other
56121        // config changes)
56122
56123
56124        segmentLoader.setAudio(true);
56125        mainSegmentLoader.setAudio(false);
56126      }
56127
56128      if (previousActiveLoader === activeGroup.playlistLoader) {
56129        // Nothing has actually changed. This can happen because track change events can fire
56130        // multiple times for a "single" change. One for enabling the new active track, and
56131        // one for disabling the track that was active
56132        startLoaders(activeGroup.playlistLoader, mediaType);
56133        return;
56134      }
56135
56136      if (segmentLoader.track) {
56137        // For WebVTT, set the new text track in the segmentloader
56138        segmentLoader.track(activeTrack);
56139      } // destructive reset
56140
56141
56142      segmentLoader.resetEverything();
56143      startLoaders(activeGroup.playlistLoader, mediaType);
56144    };
56145  };
56146
56147  var onError = {
56148    /**
56149     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
56150     * an error.
56151     *
56152     * @param {string} type
56153     *        MediaGroup type
56154     * @param {Object} settings
56155     *        Object containing required information for media groups
56156     * @return {Function}
56157     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
56158     *         console and switches back to default audio track.
56159     * @function onError.AUDIO
56160     */
56161    AUDIO: function AUDIO(type, settings) {
56162      return function () {
56163        var segmentLoader = settings.segmentLoaders[type],
56164            mediaType = settings.mediaTypes[type],
56165            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
56166        stopLoaders(segmentLoader, mediaType); // switch back to default audio track
56167
56168        var activeTrack = mediaType.activeTrack();
56169        var activeGroup = mediaType.activeGroup();
56170        var id = (activeGroup.filter(function (group) {
56171          return group["default"];
56172        })[0] || activeGroup[0]).id;
56173        var defaultTrack = mediaType.tracks[id];
56174
56175        if (activeTrack === defaultTrack) {
56176          // Default track encountered an error. All we can do now is blacklist the current
56177          // rendition and hope another will switch audio groups
56178          blacklistCurrentPlaylist({
56179            message: 'Problem encountered loading the default audio track.'
56180          });
56181          return;
56182        }
56183
56184        videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
56185
56186        for (var trackId in mediaType.tracks) {
56187          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
56188        }
56189
56190        mediaType.onTrackChanged();
56191      };
56192    },
56193
56194    /**
56195     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
56196     * an error.
56197     *
56198     * @param {string} type
56199     *        MediaGroup type
56200     * @param {Object} settings
56201     *        Object containing required information for media groups
56202     * @return {Function}
56203     *         Error handler. Logs warning to console and disables the active subtitle track
56204     * @function onError.SUBTITLES
56205     */
56206    SUBTITLES: function SUBTITLES(type, settings) {
56207      return function () {
56208        var segmentLoader = settings.segmentLoaders[type],
56209            mediaType = settings.mediaTypes[type];
56210        videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
56211        stopLoaders(segmentLoader, mediaType);
56212        var track = mediaType.activeTrack();
56213
56214        if (track) {
56215          track.mode = 'disabled';
56216        }
56217
56218        mediaType.onTrackChanged();
56219      };
56220    }
56221  };
56222  var setupListeners = {
56223    /**
56224     * Setup event listeners for audio playlist loader
56225     *
56226     * @param {string} type
56227     *        MediaGroup type
56228     * @param {PlaylistLoader|null} playlistLoader
56229     *        PlaylistLoader to register listeners on
56230     * @param {Object} settings
56231     *        Object containing required information for media groups
56232     * @function setupListeners.AUDIO
56233     */
56234    AUDIO: function AUDIO(type, playlistLoader, settings) {
56235      if (!playlistLoader) {
56236        // no playlist loader means audio will be muxed with the video
56237        return;
56238      }
56239
56240      var tech = settings.tech,
56241          requestOptions = settings.requestOptions,
56242          segmentLoader = settings.segmentLoaders[type];
56243      playlistLoader.on('loadedmetadata', function () {
56244        var media = playlistLoader.media();
56245        segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
56246        // permits, start downloading segments
56247
56248        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
56249          segmentLoader.load();
56250        }
56251      });
56252      playlistLoader.on('loadedplaylist', function () {
56253        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
56254
56255        if (!tech.paused()) {
56256          segmentLoader.load();
56257        }
56258      });
56259      playlistLoader.on('error', onError[type](type, settings));
56260    },
56261
56262    /**
56263     * Setup event listeners for subtitle playlist loader
56264     *
56265     * @param {string} type
56266     *        MediaGroup type
56267     * @param {PlaylistLoader|null} playlistLoader
56268     *        PlaylistLoader to register listeners on
56269     * @param {Object} settings
56270     *        Object containing required information for media groups
56271     * @function setupListeners.SUBTITLES
56272     */
56273    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
56274      var tech = settings.tech,
56275          requestOptions = settings.requestOptions,
56276          segmentLoader = settings.segmentLoaders[type],
56277          mediaType = settings.mediaTypes[type];
56278      playlistLoader.on('loadedmetadata', function () {
56279        var media = playlistLoader.media();
56280        segmentLoader.playlist(media, requestOptions);
56281        segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
56282        // permits, start downloading segments
56283
56284        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
56285          segmentLoader.load();
56286        }
56287      });
56288      playlistLoader.on('loadedplaylist', function () {
56289        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
56290
56291        if (!tech.paused()) {
56292          segmentLoader.load();
56293        }
56294      });
56295      playlistLoader.on('error', onError[type](type, settings));
56296    }
56297  };
56298  var initialize = {
56299    /**
56300     * Setup PlaylistLoaders and AudioTracks for the audio groups
56301     *
56302     * @param {string} type
56303     *        MediaGroup type
56304     * @param {Object} settings
56305     *        Object containing required information for media groups
56306     * @function initialize.AUDIO
56307     */
56308    'AUDIO': function AUDIO(type, settings) {
56309      var vhs = settings.vhs,
56310          sourceType = settings.sourceType,
56311          segmentLoader = settings.segmentLoaders[type],
56312          requestOptions = settings.requestOptions,
56313          _settings$master = settings.master,
56314          mediaGroups = _settings$master.mediaGroups,
56315          playlists = _settings$master.playlists,
56316          _settings$mediaTypes$ = settings.mediaTypes[type],
56317          groups = _settings$mediaTypes$.groups,
56318          tracks = _settings$mediaTypes$.tracks,
56319          masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
56320
56321      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
56322        mediaGroups[type] = {
56323          main: {
56324            "default": {
56325              "default": true
56326            }
56327          }
56328        };
56329      }
56330
56331      var _loop = function _loop(groupId) {
56332        if (!groups[groupId]) {
56333          groups[groupId] = [];
56334        } // List of playlists that have an AUDIO attribute value matching the current
56335        // group ID
56336
56337
56338        var groupPlaylists = playlists.filter(function (playlist) {
56339          return playlist.attributes[type] === groupId;
56340        });
56341
56342        var _loop2 = function _loop2(variantLabel) {
56343          var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
56344          // this alternate audio variant
56345
56346          var matchingPlaylists = groupPlaylists.filter(function (playlist) {
56347            return playlist.resolvedUri === properties.resolvedUri;
56348          });
56349
56350          if (matchingPlaylists.length) {
56351            // If there is a playlist that has the same uri as this audio variant, assume
56352            // that the playlist is audio only. We delete the resolvedUri property here
56353            // to prevent a playlist loader from being created so that we don't have
56354            // both the main and audio segment loaders loading the same audio segments
56355            // from the same playlist.
56356            delete properties.resolvedUri;
56357          }
56358
56359          var playlistLoader = void 0; // if vhs-json was provided as the source, and the media playlist was resolved,
56360          // use the resolved media playlist object
56361
56362          if (sourceType === 'vhs-json' && properties.playlists) {
56363            playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
56364          } else if (properties.resolvedUri) {
56365            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
56366          } else if (properties.playlists && sourceType === 'dash') {
56367            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
56368          } else {
56369            // no resolvedUri means the audio is muxed with the video when using this
56370            // audio track
56371            playlistLoader = null;
56372          }
56373
56374          properties = videojs$1.mergeOptions({
56375            id: variantLabel,
56376            playlistLoader: playlistLoader
56377          }, properties);
56378          setupListeners[type](type, properties.playlistLoader, settings);
56379          groups[groupId].push(properties);
56380
56381          if (typeof tracks[variantLabel] === 'undefined') {
56382            var track = new videojs$1.AudioTrack({
56383              id: variantLabel,
56384              kind: audioTrackKind_(properties),
56385              enabled: false,
56386              language: properties.language,
56387              "default": properties["default"],
56388              label: variantLabel
56389            });
56390            tracks[variantLabel] = track;
56391          }
56392        };
56393
56394        for (var variantLabel in mediaGroups[type][groupId]) {
56395          _loop2(variantLabel);
56396        }
56397      };
56398
56399      for (var groupId in mediaGroups[type]) {
56400        _loop(groupId);
56401      } // setup single error event handler for the segment loader
56402
56403
56404      segmentLoader.on('error', onError[type](type, settings));
56405    },
56406
56407    /**
56408     * Setup PlaylistLoaders and TextTracks for the subtitle groups
56409     *
56410     * @param {string} type
56411     *        MediaGroup type
56412     * @param {Object} settings
56413     *        Object containing required information for media groups
56414     * @function initialize.SUBTITLES
56415     */
56416    'SUBTITLES': function SUBTITLES(type, settings) {
56417      var tech = settings.tech,
56418          vhs = settings.vhs,
56419          sourceType = settings.sourceType,
56420          segmentLoader = settings.segmentLoaders[type],
56421          requestOptions = settings.requestOptions,
56422          mediaGroups = settings.master.mediaGroups,
56423          _settings$mediaTypes$2 = settings.mediaTypes[type],
56424          groups = _settings$mediaTypes$2.groups,
56425          tracks = _settings$mediaTypes$2.tracks,
56426          masterPlaylistLoader = settings.masterPlaylistLoader;
56427
56428      for (var groupId in mediaGroups[type]) {
56429        if (!groups[groupId]) {
56430          groups[groupId] = [];
56431        }
56432
56433        for (var variantLabel in mediaGroups[type][groupId]) {
56434          if (mediaGroups[type][groupId][variantLabel].forced) {
56435            // Subtitle playlists with the forced attribute are not selectable in Safari.
56436            // According to Apple's HLS Authoring Specification:
56437            //   If content has forced subtitles and regular subtitles in a given language,
56438            //   the regular subtitles track in that language MUST contain both the forced
56439            //   subtitles and the regular subtitles for that language.
56440            // Because of this requirement and that Safari does not add forced subtitles,
56441            // forced subtitles are skipped here to maintain consistent experience across
56442            // all platforms
56443            continue;
56444          }
56445
56446          var properties = mediaGroups[type][groupId][variantLabel];
56447          var playlistLoader = void 0;
56448
56449          if (sourceType === 'hls') {
56450            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
56451          } else if (sourceType === 'dash') {
56452            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
56453          } else if (sourceType === 'vhs-json') {
56454            playlistLoader = new PlaylistLoader( // if the vhs-json object included the media playlist, use the media playlist
56455            // as provided, otherwise use the resolved URI to load the playlist
56456            properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
56457          }
56458
56459          properties = videojs$1.mergeOptions({
56460            id: variantLabel,
56461            playlistLoader: playlistLoader
56462          }, properties);
56463          setupListeners[type](type, properties.playlistLoader, settings);
56464          groups[groupId].push(properties);
56465
56466          if (typeof tracks[variantLabel] === 'undefined') {
56467            var track = tech.addRemoteTextTrack({
56468              id: variantLabel,
56469              kind: 'subtitles',
56470              "default": properties["default"] && properties.autoselect,
56471              language: properties.language,
56472              label: variantLabel
56473            }, false).track;
56474            tracks[variantLabel] = track;
56475          }
56476        }
56477      } // setup single error event handler for the segment loader
56478
56479
56480      segmentLoader.on('error', onError[type](type, settings));
56481    },
56482
56483    /**
56484     * Setup TextTracks for the closed-caption groups
56485     *
56486     * @param {String} type
56487     *        MediaGroup type
56488     * @param {Object} settings
56489     *        Object containing required information for media groups
56490     * @function initialize['CLOSED-CAPTIONS']
56491     */
56492    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
56493      var tech = settings.tech,
56494          mediaGroups = settings.master.mediaGroups,
56495          _settings$mediaTypes$3 = settings.mediaTypes[type],
56496          groups = _settings$mediaTypes$3.groups,
56497          tracks = _settings$mediaTypes$3.tracks;
56498
56499      for (var groupId in mediaGroups[type]) {
56500        if (!groups[groupId]) {
56501          groups[groupId] = [];
56502        }
56503
56504        for (var variantLabel in mediaGroups[type][groupId]) {
56505          var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
56506          // doesn't use a CCx INSTREAM-ID
56507
56508          if (!properties.instreamId.match(/CC\d/)) {
56509            continue;
56510          } // No PlaylistLoader is required for Closed-Captions because the captions are
56511          // embedded within the video stream
56512
56513
56514          groups[groupId].push(videojs$1.mergeOptions({
56515            id: variantLabel
56516          }, properties));
56517
56518          if (typeof tracks[variantLabel] === 'undefined') {
56519            var track = tech.addRemoteTextTrack({
56520              id: properties.instreamId,
56521              kind: 'captions',
56522              "default": properties["default"] && properties.autoselect,
56523              language: properties.language,
56524              label: variantLabel
56525            }, false).track;
56526            tracks[variantLabel] = track;
56527          }
56528        }
56529      }
56530    }
56531  };
56532  /**
56533   * Returns a function used to get the active group of the provided type
56534   *
56535   * @param {string} type
56536   *        MediaGroup type
56537   * @param {Object} settings
56538   *        Object containing required information for media groups
56539   * @return {Function}
56540   *         Function that returns the active media group for the provided type. Takes an
56541   *         optional parameter {TextTrack} track. If no track is provided, a list of all
56542   *         variants in the group, otherwise the variant corresponding to the provided
56543   *         track is returned.
56544   * @function activeGroup
56545   */
56546
56547  var activeGroup = function activeGroup(type, settings) {
56548    return function (track) {
56549      var masterPlaylistLoader = settings.masterPlaylistLoader,
56550          groups = settings.mediaTypes[type].groups;
56551      var media = masterPlaylistLoader.media();
56552
56553      if (!media) {
56554        return null;
56555      }
56556
56557      var variants = null;
56558
56559      if (media.attributes[type]) {
56560        variants = groups[media.attributes[type]];
56561      }
56562
56563      variants = variants || groups.main;
56564
56565      if (typeof track === 'undefined') {
56566        return variants;
56567      }
56568
56569      if (track === null) {
56570        // An active track was specified so a corresponding group is expected. track === null
56571        // means no track is currently active so there is no corresponding group
56572        return null;
56573      }
56574
56575      return variants.filter(function (props) {
56576        return props.id === track.id;
56577      })[0] || null;
56578    };
56579  };
56580
56581  var activeTrack = {
56582    /**
56583     * Returns a function used to get the active track of type provided
56584     *
56585     * @param {string} type
56586     *        MediaGroup type
56587     * @param {Object} settings
56588     *        Object containing required information for media groups
56589     * @return {Function}
56590     *         Function that returns the active media track for the provided type. Returns
56591     *         null if no track is active
56592     * @function activeTrack.AUDIO
56593     */
56594    AUDIO: function AUDIO(type, settings) {
56595      return function () {
56596        var tracks = settings.mediaTypes[type].tracks;
56597
56598        for (var id in tracks) {
56599          if (tracks[id].enabled) {
56600            return tracks[id];
56601          }
56602        }
56603
56604        return null;
56605      };
56606    },
56607
56608    /**
56609     * Returns a function used to get the active track of type provided
56610     *
56611     * @param {string} type
56612     *        MediaGroup type
56613     * @param {Object} settings
56614     *        Object containing required information for media groups
56615     * @return {Function}
56616     *         Function that returns the active media track for the provided type. Returns
56617     *         null if no track is active
56618     * @function activeTrack.SUBTITLES
56619     */
56620    SUBTITLES: function SUBTITLES(type, settings) {
56621      return function () {
56622        var tracks = settings.mediaTypes[type].tracks;
56623
56624        for (var id in tracks) {
56625          if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
56626            return tracks[id];
56627          }
56628        }
56629
56630        return null;
56631      };
56632    }
56633  };
56634  /**
56635   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
56636   * Closed-Captions) specified in the master manifest.
56637   *
56638   * @param {Object} settings
56639   *        Object containing required information for setting up the media groups
56640   * @param {Tech} settings.tech
56641   *        The tech of the player
56642   * @param {Object} settings.requestOptions
56643   *        XHR request options used by the segment loaders
56644   * @param {PlaylistLoader} settings.masterPlaylistLoader
56645   *        PlaylistLoader for the master source
56646   * @param {VhsHandler} settings.vhs
56647   *        VHS SourceHandler
56648   * @param {Object} settings.master
56649   *        The parsed master manifest
56650   * @param {Object} settings.mediaTypes
56651   *        Object to store the loaders, tracks, and utility methods for each media type
56652   * @param {Function} settings.blacklistCurrentPlaylist
56653   *        Blacklists the current rendition and forces a rendition switch.
56654   * @function setupMediaGroups
56655   */
56656
56657  var setupMediaGroups = function setupMediaGroups(settings) {
56658    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
56659      initialize[type](type, settings);
56660    });
56661    var mediaTypes = settings.mediaTypes,
56662        masterPlaylistLoader = settings.masterPlaylistLoader,
56663        tech = settings.tech,
56664        vhs = settings.vhs; // setup active group and track getters and change event handlers
56665
56666    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
56667      mediaTypes[type].activeGroup = activeGroup(type, settings);
56668      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
56669      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
56670      mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
56671      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
56672    }); // DO NOT enable the default subtitle or caption track.
56673    // DO enable the default audio track
56674
56675    var audioGroup = mediaTypes.AUDIO.activeGroup();
56676
56677    if (audioGroup) {
56678      var groupId = (audioGroup.filter(function (group) {
56679        return group["default"];
56680      })[0] || audioGroup[0]).id;
56681      mediaTypes.AUDIO.tracks[groupId].enabled = true;
56682      mediaTypes.AUDIO.onTrackChanged();
56683    }
56684
56685    masterPlaylistLoader.on('mediachange', function () {
56686      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
56687        return mediaTypes[type].onGroupChanged();
56688      });
56689    });
56690    masterPlaylistLoader.on('mediachanging', function () {
56691      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
56692        return mediaTypes[type].onGroupChanging();
56693      });
56694    }); // custom audio track change event handler for usage event
56695
56696    var onAudioTrackChanged = function onAudioTrackChanged() {
56697      mediaTypes.AUDIO.onTrackChanged();
56698      tech.trigger({
56699        type: 'usage',
56700        name: 'vhs-audio-change'
56701      });
56702      tech.trigger({
56703        type: 'usage',
56704        name: 'hls-audio-change'
56705      });
56706    };
56707
56708    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
56709    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
56710    vhs.on('dispose', function () {
56711      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
56712      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
56713    }); // clear existing audio tracks and add the ones we just created
56714
56715    tech.clearTracks('audio');
56716
56717    for (var id in mediaTypes.AUDIO.tracks) {
56718      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
56719    }
56720  };
56721  /**
56722   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
56723   * media type
56724   *
56725   * @return {Object}
56726   *         Object to store the loaders, tracks, and utility methods for each media type
56727   * @function createMediaTypes
56728   */
56729
56730
56731  var createMediaTypes = function createMediaTypes() {
56732    var mediaTypes = {};
56733    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
56734      mediaTypes[type] = {
56735        groups: {},
56736        tracks: {},
56737        activePlaylistLoader: null,
56738        activeGroup: noop$1,
56739        activeTrack: noop$1,
56740        onGroupChanged: noop$1,
56741        onTrackChanged: noop$1
56742      };
56743    });
56744    return mediaTypes;
56745  };
56746
56747  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
56748  var Vhs; // SegmentLoader stats that need to have each loader's
56749  // values summed to calculate the final value
56750
56751  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
56752
56753  var sumLoaderStat = function sumLoaderStat(stat) {
56754    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
56755  };
56756
56757  var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) {
56758    var currentPlaylist = _ref.currentPlaylist,
56759        nextPlaylist = _ref.nextPlaylist,
56760        forwardBuffer = _ref.forwardBuffer,
56761        bufferLowWaterLine = _ref.bufferLowWaterLine,
56762        duration = _ref.duration,
56763        log = _ref.log; // we have no other playlist to switch to
56764
56765    if (!nextPlaylist) {
56766      videojs$1.log.warn('We received no playlist to switch to. Please check your stream.');
56767      return false;
56768    } // If the playlist is live, then we want to not take low water line into account.
56769    // This is because in LIVE, the player plays 3 segments from the end of the
56770    // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
56771    // in those segments, a viewer will never experience a rendition upswitch.
56772
56773
56774    if (!currentPlaylist.endList) {
56775      return true;
56776    } // For the same reason as LIVE, we ignore the low water line when the VOD
56777    // duration is below the max potential low water line
56778
56779
56780    if (duration < Config.MAX_BUFFER_LOW_WATER_LINE) {
56781      return true;
56782    } // we want to switch down to lower resolutions quickly to continue playback, but
56783
56784
56785    if (nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH) {
56786      return true;
56787    } // ensure we have some buffer before we switch up to prevent us running out of
56788    // buffer while loading a higher rendition.
56789
56790
56791    if (forwardBuffer >= bufferLowWaterLine) {
56792      return true;
56793    }
56794
56795    return false;
56796  };
56797  /**
56798   * the master playlist controller controller all interactons
56799   * between playlists and segmentloaders. At this time this mainly
56800   * involves a master playlist and a series of audio playlists
56801   * if they are available
56802   *
56803   * @class MasterPlaylistController
56804   * @extends videojs.EventTarget
56805   */
56806
56807
56808  var MasterPlaylistController = /*#__PURE__*/function (_videojs$EventTarget) {
56809    inheritsLoose(MasterPlaylistController, _videojs$EventTarget);
56810
56811    function MasterPlaylistController(options) {
56812      var _this;
56813
56814      _this = _videojs$EventTarget.call(this) || this;
56815      var src = options.src,
56816          handleManifestRedirects = options.handleManifestRedirects,
56817          withCredentials = options.withCredentials,
56818          tech = options.tech,
56819          bandwidth = options.bandwidth,
56820          externVhs = options.externVhs,
56821          useCueTags = options.useCueTags,
56822          blacklistDuration = options.blacklistDuration,
56823          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
56824          sourceType = options.sourceType,
56825          cacheEncryptionKeys = options.cacheEncryptionKeys,
56826          handlePartialData = options.handlePartialData;
56827
56828      if (!src) {
56829        throw new Error('A non-empty playlist URL or JSON manifest string is required');
56830      }
56831
56832      Vhs = externVhs;
56833      _this.withCredentials = withCredentials;
56834      _this.tech_ = tech;
56835      _this.vhs_ = tech.vhs;
56836      _this.sourceType_ = sourceType;
56837      _this.useCueTags_ = useCueTags;
vendor: 51,539 bytes, lines 56838-58293
56838      _this.blacklistDuration = blacklistDuration;
56839      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
56840
56841      if (_this.useCueTags_) {
56842        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
56843        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
56844      }
56845
56846      _this.requestOptions_ = {
56847        withCredentials: withCredentials,
56848        handleManifestRedirects: handleManifestRedirects,
56849        timeout: null
56850      };
56851
56852      _this.on('error', _this.pauseLoading);
56853
56854      _this.mediaTypes_ = createMediaTypes();
56855      _this.mediaSource = new window$1.MediaSource();
56856      _this.handleDurationChange_ = _this.handleDurationChange_.bind(assertThisInitialized(_this));
56857      _this.handleSourceOpen_ = _this.handleSourceOpen_.bind(assertThisInitialized(_this));
56858      _this.handleSourceEnded_ = _this.handleSourceEnded_.bind(assertThisInitialized(_this));
56859
56860      _this.mediaSource.addEventListener('durationchange', _this.handleDurationChange_); // load the media source into the player
56861
56862
56863      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_);
56864
56865      _this.mediaSource.addEventListener('sourceended', _this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
56866      // everything, and the MediaSource should not be detached without a proper disposal
56867
56868
56869      _this.seekable_ = videojs$1.createTimeRanges();
56870      _this.hasPlayed_ = false;
56871      _this.syncController_ = new SyncController(options);
56872      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
56873        kind: 'metadata',
56874        label: 'segment-metadata'
56875      }, false).track;
56876      _this.decrypter_ = new Decrypter();
56877      _this.sourceUpdater_ = new SourceUpdater(_this.mediaSource);
56878      _this.inbandTextTracks_ = {};
56879      _this.timelineChangeController_ = new TimelineChangeController();
56880      var segmentLoaderSettings = {
56881        vhs: _this.vhs_,
56882        mediaSource: _this.mediaSource,
56883        currentTime: _this.tech_.currentTime.bind(_this.tech_),
56884        seekable: function seekable() {
56885          return _this.seekable();
56886        },
56887        seeking: function seeking() {
56888          return _this.tech_.seeking();
56889        },
56890        duration: function duration() {
56891          return _this.duration();
56892        },
56893        hasPlayed: function hasPlayed() {
56894          return _this.hasPlayed_;
56895        },
56896        goalBufferLength: function goalBufferLength() {
56897          return _this.goalBufferLength();
56898        },
56899        bandwidth: bandwidth,
56900        syncController: _this.syncController_,
56901        decrypter: _this.decrypter_,
56902        sourceType: _this.sourceType_,
56903        inbandTextTracks: _this.inbandTextTracks_,
56904        cacheEncryptionKeys: cacheEncryptionKeys,
56905        handlePartialData: handlePartialData,
56906        sourceUpdater: _this.sourceUpdater_,
56907        timelineChangeController: _this.timelineChangeController_
56908      }; // The source type check not only determines whether a special DASH playlist loader
56909      // should be used, but also covers the case where the provided src is a vhs-json
56910      // manifest object (instead of a URL). In the case of vhs-json, the default
56911      // PlaylistLoader should be used.
56912
56913      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, _this.vhs_, _this.requestOptions_) : new PlaylistLoader(src, _this.vhs_, _this.requestOptions_);
56914
56915      _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
56916      // combined audio/video or just video when alternate audio track is selected
56917
56918
56919      _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56920        segmentMetadataTrack: _this.segmentMetadataTrack_,
56921        loaderType: 'main'
56922      }), options); // alternate audio track
56923
56924      _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56925        loaderType: 'audio'
56926      }), options);
56927      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56928        loaderType: 'vtt',
56929        featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks
56930      }), options);
56931
56932      _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
56933      // mediaRequests_
56934      // mediaRequestsAborted_
56935      // mediaRequestsTimedout_
56936      // mediaRequestsErrored_
56937      // mediaTransferDuration_
56938      // mediaBytesTransferred_
56939
56940
56941      loaderStats.forEach(function (stat) {
56942        _this[stat + '_'] = sumLoaderStat.bind(assertThisInitialized(_this), stat);
56943      });
56944      _this.logger_ = logger('MPC');
56945      _this.triggeredFmp4Usage = false;
56946
56947      _this.masterPlaylistLoader_.load();
56948
56949      return _this;
56950    }
56951    /**
56952     * Register event handlers on the master playlist loader. A helper
56953     * function for construction time.
56954     *
56955     * @private
56956     */
56957
56958
56959    var _proto = MasterPlaylistController.prototype;
56960
56961    _proto.setupMasterPlaylistLoaderListeners_ = function setupMasterPlaylistLoaderListeners_() {
56962      var _this2 = this;
56963
56964      this.masterPlaylistLoader_.on('loadedmetadata', function () {
56965        var media = _this2.masterPlaylistLoader_.media();
56966
56967        var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
56968        // timeout the request.
56969
56970        if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
56971          _this2.requestOptions_.timeout = 0;
56972        } else {
56973          _this2.requestOptions_.timeout = requestTimeout;
56974        } // if this isn't a live video and preload permits, start
56975        // downloading segments
56976
56977
56978        if (media.endList && _this2.tech_.preload() !== 'none') {
56979          _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
56980
56981          _this2.mainSegmentLoader_.load();
56982        }
56983
56984        setupMediaGroups({
56985          sourceType: _this2.sourceType_,
56986          segmentLoaders: {
56987            AUDIO: _this2.audioSegmentLoader_,
56988            SUBTITLES: _this2.subtitleSegmentLoader_,
56989            main: _this2.mainSegmentLoader_
56990          },
56991          tech: _this2.tech_,
56992          requestOptions: _this2.requestOptions_,
56993          masterPlaylistLoader: _this2.masterPlaylistLoader_,
56994          vhs: _this2.vhs_,
56995          master: _this2.master(),
56996          mediaTypes: _this2.mediaTypes_,
56997          blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
56998        });
56999
57000        _this2.triggerPresenceUsage_(_this2.master(), media);
57001
57002        _this2.setupFirstPlay();
57003
57004        if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
57005          _this2.trigger('selectedinitialmedia');
57006        } else {
57007          // We must wait for the active audio playlist loader to
57008          // finish setting up before triggering this event so the
57009          // representations API and EME setup is correct
57010          _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
57011            _this2.trigger('selectedinitialmedia');
57012          });
57013        }
57014      });
57015      this.masterPlaylistLoader_.on('loadedplaylist', function () {
57016        var updatedPlaylist = _this2.masterPlaylistLoader_.media();
57017
57018        if (!updatedPlaylist) {
57019          // exclude any variants that are not supported by the browser before selecting
57020          // an initial media as the playlist selectors do not consider browser support
57021          _this2.excludeUnsupportedVariants_();
57022
57023          var selectedMedia;
57024
57025          if (_this2.enableLowInitialPlaylist) {
57026            selectedMedia = _this2.selectInitialPlaylist();
57027          }
57028
57029          if (!selectedMedia) {
57030            selectedMedia = _this2.selectPlaylist();
57031          }
57032
57033          _this2.initialMedia_ = selectedMedia;
57034
57035          _this2.masterPlaylistLoader_.media(_this2.initialMedia_); // Under the standard case where a source URL is provided, loadedplaylist will
57036          // fire again since the playlist will be requested. In the case of vhs-json
57037          // (where the manifest object is provided as the source), when the media
57038          // playlist's `segments` list is already available, a media playlist won't be
57039          // requested, and loadedplaylist won't fire again, so the playlist handler must be
57040          // called on its own here.
57041
57042
57043          var haveJsonSource = _this2.sourceType_ === 'vhs-json' && _this2.initialMedia_.segments;
57044
57045          if (!haveJsonSource) {
57046            return;
57047          }
57048
57049          updatedPlaylist = _this2.initialMedia_;
57050        }
57051
57052        _this2.handleUpdatedMediaPlaylist(updatedPlaylist);
57053      });
57054      this.masterPlaylistLoader_.on('error', function () {
57055        _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
57056      });
57057      this.masterPlaylistLoader_.on('mediachanging', function () {
57058        _this2.mainSegmentLoader_.abort();
57059
57060        _this2.mainSegmentLoader_.pause();
57061      });
57062      this.masterPlaylistLoader_.on('mediachange', function () {
57063        var media = _this2.masterPlaylistLoader_.media();
57064
57065        var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
57066        // timeout the request.
57067
57068        if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
57069          _this2.requestOptions_.timeout = 0;
57070        } else {
57071          _this2.requestOptions_.timeout = requestTimeout;
57072        } // TODO: Create a new event on the PlaylistLoader that signals
57073        // that the segments have changed in some way and use that to
57074        // update the SegmentLoader instead of doing it twice here and
57075        // on `loadedplaylist`
57076
57077
57078        _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
57079
57080        _this2.mainSegmentLoader_.load();
57081
57082        _this2.tech_.trigger({
57083          type: 'mediachange',
57084          bubbles: true
57085        });
57086      });
57087      this.masterPlaylistLoader_.on('playlistunchanged', function () {
57088        var updatedPlaylist = _this2.masterPlaylistLoader_.media();
57089
57090        var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
57091
57092        if (playlistOutdated) {
57093          // Playlist has stopped updating and we're stuck at its end. Try to
57094          // blacklist it and switch to another playlist in the hope that that
57095          // one is updating (and give the player a chance to re-adjust to the
57096          // safe live point).
57097          _this2.blacklistCurrentPlaylist({
57098            message: 'Playlist no longer updating.'
57099          }); // useful for monitoring QoS
57100
57101
57102          _this2.tech_.trigger('playliststuck');
57103        }
57104      });
57105      this.masterPlaylistLoader_.on('renditiondisabled', function () {
57106        _this2.tech_.trigger({
57107          type: 'usage',
57108          name: 'vhs-rendition-disabled'
57109        });
57110
57111        _this2.tech_.trigger({
57112          type: 'usage',
57113          name: 'hls-rendition-disabled'
57114        });
57115      });
57116      this.masterPlaylistLoader_.on('renditionenabled', function () {
57117        _this2.tech_.trigger({
57118          type: 'usage',
57119          name: 'vhs-rendition-enabled'
57120        });
57121
57122        _this2.tech_.trigger({
57123          type: 'usage',
57124          name: 'hls-rendition-enabled'
57125        });
57126      });
57127    }
57128    /**
57129     * Given an updated media playlist (whether it was loaded for the first time, or
57130     * refreshed for live playlists), update any relevant properties and state to reflect
57131     * changes in the media that should be accounted for (e.g., cues and duration).
57132     *
57133     * @param {Object} updatedPlaylist the updated media playlist object
57134     *
57135     * @private
57136     */
57137    ;
57138
57139    _proto.handleUpdatedMediaPlaylist = function handleUpdatedMediaPlaylist(updatedPlaylist) {
57140      if (this.useCueTags_) {
57141        this.updateAdCues_(updatedPlaylist);
57142      } // TODO: Create a new event on the PlaylistLoader that signals
57143      // that the segments have changed in some way and use that to
57144      // update the SegmentLoader instead of doing it twice here and
57145      // on `mediachange`
57146
57147
57148      this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
57149      this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
57150      // as it is possible that it was temporarily stopped while waiting for
57151      // a playlist (e.g., in case the playlist errored and we re-requested it).
57152
57153      if (!this.tech_.paused()) {
57154        this.mainSegmentLoader_.load();
57155
57156        if (this.audioSegmentLoader_) {
57157          this.audioSegmentLoader_.load();
57158        }
57159      }
57160    }
57161    /**
57162     * A helper function for triggerring presence usage events once per source
57163     *
57164     * @private
57165     */
57166    ;
57167
57168    _proto.triggerPresenceUsage_ = function triggerPresenceUsage_(master, media) {
57169      var mediaGroups = master.mediaGroups || {};
57170      var defaultDemuxed = true;
57171      var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
57172
57173      for (var mediaGroup in mediaGroups.AUDIO) {
57174        for (var label in mediaGroups.AUDIO[mediaGroup]) {
57175          var properties = mediaGroups.AUDIO[mediaGroup][label];
57176
57177          if (!properties.uri) {
57178            defaultDemuxed = false;
57179          }
57180        }
57181      }
57182
57183      if (defaultDemuxed) {
57184        this.tech_.trigger({
57185          type: 'usage',
57186          name: 'vhs-demuxed'
57187        });
57188        this.tech_.trigger({
57189          type: 'usage',
57190          name: 'hls-demuxed'
57191        });
57192      }
57193
57194      if (Object.keys(mediaGroups.SUBTITLES).length) {
57195        this.tech_.trigger({
57196          type: 'usage',
57197          name: 'vhs-webvtt'
57198        });
57199        this.tech_.trigger({
57200          type: 'usage',
57201          name: 'hls-webvtt'
57202        });
57203      }
57204
57205      if (Vhs.Playlist.isAes(media)) {
57206        this.tech_.trigger({
57207          type: 'usage',
57208          name: 'vhs-aes'
57209        });
57210        this.tech_.trigger({
57211          type: 'usage',
57212          name: 'hls-aes'
57213        });
57214      }
57215
57216      if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
57217        this.tech_.trigger({
57218          type: 'usage',
57219          name: 'vhs-alternate-audio'
57220        });
57221        this.tech_.trigger({
57222          type: 'usage',
57223          name: 'hls-alternate-audio'
57224        });
57225      }
57226
57227      if (this.useCueTags_) {
57228        this.tech_.trigger({
57229          type: 'usage',
57230          name: 'vhs-playlist-cue-tags'
57231        });
57232        this.tech_.trigger({
57233          type: 'usage',
57234          name: 'hls-playlist-cue-tags'
57235        });
57236      }
57237    }
57238    /**
57239     * Register event handlers on the segment loaders. A helper function
57240     * for construction time.
57241     *
57242     * @private
57243     */
57244    ;
57245
57246    _proto.setupSegmentLoaderListeners_ = function setupSegmentLoaderListeners_() {
57247      var _this3 = this;
57248
57249      this.mainSegmentLoader_.on('bandwidthupdate', function () {
57250        var nextPlaylist = _this3.selectPlaylist();
57251
57252        var currentPlaylist = _this3.masterPlaylistLoader_.media();
57253
57254        var buffered = _this3.tech_.buffered();
57255
57256        var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
57257
57258        var bufferLowWaterLine = _this3.bufferLowWaterLine();
57259
57260        if (shouldSwitchToMedia({
57261          currentPlaylist: currentPlaylist,
57262          nextPlaylist: nextPlaylist,
57263          forwardBuffer: forwardBuffer,
57264          bufferLowWaterLine: bufferLowWaterLine,
57265          duration: _this3.duration(),
57266          log: _this3.logger_
57267        })) {
57268          _this3.masterPlaylistLoader_.media(nextPlaylist);
57269        }
57270
57271        _this3.tech_.trigger('bandwidthupdate');
57272      });
57273      this.mainSegmentLoader_.on('progress', function () {
57274        _this3.trigger('progress');
57275      });
57276      this.mainSegmentLoader_.on('error', function () {
57277        _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
57278      });
57279      this.mainSegmentLoader_.on('appenderror', function () {
57280        _this3.error = _this3.mainSegmentLoader_.error_;
57281
57282        _this3.trigger('error');
57283      });
57284      this.mainSegmentLoader_.on('syncinfoupdate', function () {
57285        _this3.onSyncInfoUpdate_();
57286      });
57287      this.mainSegmentLoader_.on('timestampoffset', function () {
57288        _this3.tech_.trigger({
57289          type: 'usage',
57290          name: 'vhs-timestamp-offset'
57291        });
57292
57293        _this3.tech_.trigger({
57294          type: 'usage',
57295          name: 'hls-timestamp-offset'
57296        });
57297      });
57298      this.audioSegmentLoader_.on('syncinfoupdate', function () {
57299        _this3.onSyncInfoUpdate_();
57300      });
57301      this.audioSegmentLoader_.on('appenderror', function () {
57302        _this3.error = _this3.audioSegmentLoader_.error_;
57303
57304        _this3.trigger('error');
57305      });
57306      this.mainSegmentLoader_.on('ended', function () {
57307        _this3.logger_('main segment loader ended');
57308
57309        _this3.onEndOfStream();
57310      });
57311      this.mainSegmentLoader_.on('earlyabort', function () {
57312        _this3.blacklistCurrentPlaylist({
57313          message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
57314        }, ABORT_EARLY_BLACKLIST_SECONDS);
57315      });
57316
57317      var updateCodecs = function updateCodecs() {
57318        if (!_this3.sourceUpdater_.ready()) {
57319          return _this3.tryToCreateSourceBuffers_();
57320        }
57321
57322        var codecs = _this3.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
57323
57324
57325        if (!codecs) {
57326          return;
57327        }
57328
57329        _this3.sourceUpdater_.addOrChangeSourceBuffers(codecs);
57330      };
57331
57332      this.mainSegmentLoader_.on('trackinfo', updateCodecs);
57333      this.audioSegmentLoader_.on('trackinfo', updateCodecs);
57334      this.mainSegmentLoader_.on('fmp4', function () {
57335        if (!_this3.triggeredFmp4Usage) {
57336          _this3.tech_.trigger({
57337            type: 'usage',
57338            name: 'vhs-fmp4'
57339          });
57340
57341          _this3.tech_.trigger({
57342            type: 'usage',
57343            name: 'hls-fmp4'
57344          });
57345
57346          _this3.triggeredFmp4Usage = true;
57347        }
57348      });
57349      this.audioSegmentLoader_.on('fmp4', function () {
57350        if (!_this3.triggeredFmp4Usage) {
57351          _this3.tech_.trigger({
57352            type: 'usage',
57353            name: 'vhs-fmp4'
57354          });
57355
57356          _this3.tech_.trigger({
57357            type: 'usage',
57358            name: 'hls-fmp4'
57359          });
57360
57361          _this3.triggeredFmp4Usage = true;
57362        }
57363      });
57364      this.audioSegmentLoader_.on('ended', function () {
57365        _this3.logger_('audioSegmentLoader ended');
57366
57367        _this3.onEndOfStream();
57368      });
57369    };
57370
57371    _proto.mediaSecondsLoaded_ = function mediaSecondsLoaded_() {
57372      return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
57373    }
57374    /**
57375     * Call load on our SegmentLoaders
57376     */
57377    ;
57378
57379    _proto.load = function load() {
57380      this.mainSegmentLoader_.load();
57381
57382      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
57383        this.audioSegmentLoader_.load();
57384      }
57385
57386      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
57387        this.subtitleSegmentLoader_.load();
57388      }
57389    }
57390    /**
57391     * Re-tune playback quality level for the current player
57392     * conditions without performing destructive actions, like
57393     * removing already buffered content
57394     *
57395     * @private
57396     */
57397    ;
57398
57399    _proto.smoothQualityChange_ = function smoothQualityChange_(media) {
57400      if (media === void 0) {
57401        media = this.selectPlaylist();
57402      }
57403
57404      if (media === this.masterPlaylistLoader_.media()) {
57405        return;
57406      }
57407
57408      this.masterPlaylistLoader_.media(media);
57409      this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
57410    }
57411    /**
57412     * Re-tune playback quality level for the current player
57413     * conditions. This method will perform destructive actions like removing
57414     * already buffered content in order to readjust the currently active
57415     * playlist quickly. This is good for manual quality changes
57416     *
57417     * @private
57418     */
57419    ;
57420
57421    _proto.fastQualityChange_ = function fastQualityChange_(media) {
57422      var _this4 = this;
57423
57424      if (media === void 0) {
57425        media = this.selectPlaylist();
57426      }
57427
57428      if (media === this.masterPlaylistLoader_.media()) {
57429        return;
57430      }
57431
57432      this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
57433      // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
57434      // ahead is roughly the minimum that will accomplish this across a variety of content
57435      // in IE and Edge, but seeking in place is sufficient on all other browsers)
57436      // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
57437      // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
57438
57439      this.mainSegmentLoader_.resetEverything(function () {
57440        // Since this is not a typical seek, we avoid the seekTo method which can cause segments
57441        // from the previously enabled rendition to load before the new playlist has finished loading
57442        if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
57443          _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
57444        } else {
57445          _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
57446        }
57447      }); // don't need to reset audio as it is reset when media changes
57448    }
57449    /**
57450     * Begin playback.
57451     */
57452    ;
57453
57454    _proto.play = function play() {
57455      if (this.setupFirstPlay()) {
57456        return;
57457      }
57458
57459      if (this.tech_.ended()) {
57460        this.tech_.setCurrentTime(0);
57461      }
57462
57463      if (this.hasPlayed_) {
57464        this.load();
57465      }
57466
57467      var seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
57468      // seek forward to the live point
57469
57470      if (this.tech_.duration() === Infinity) {
57471        if (this.tech_.currentTime() < seekable.start(0)) {
57472          return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
57473        }
57474      }
57475    }
57476    /**
57477     * Seek to the latest media position if this is a live video and the
57478     * player and video are loaded and initialized.
57479     */
57480    ;
57481
57482    _proto.setupFirstPlay = function setupFirstPlay() {
57483      var _this5 = this;
57484
57485      var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
57486      //  If 1) there is no active media
57487      //     2) the player is paused
57488      //     3) the first play has already been setup
57489      // then exit early
57490
57491      if (!media || this.tech_.paused() || this.hasPlayed_) {
57492        return false;
57493      } // when the video is a live stream
57494
57495
57496      if (!media.endList) {
57497        var seekable = this.seekable();
57498
57499        if (!seekable.length) {
57500          // without a seekable range, the player cannot seek to begin buffering at the live
57501          // point
57502          return false;
57503        }
57504
57505        if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
57506          // IE11 throws an InvalidStateError if you try to set currentTime while the
57507          // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
57508          this.tech_.one('loadedmetadata', function () {
57509            _this5.trigger('firstplay');
57510
57511            _this5.tech_.setCurrentTime(seekable.end(0));
57512
57513            _this5.hasPlayed_ = true;
57514          });
57515          return false;
57516        } // trigger firstplay to inform the source handler to ignore the next seek event
57517
57518
57519        this.trigger('firstplay'); // seek to the live point
57520
57521        this.tech_.setCurrentTime(seekable.end(0));
57522      }
57523
57524      this.hasPlayed_ = true; // we can begin loading now that everything is ready
57525
57526      this.load();
57527      return true;
57528    }
57529    /**
57530     * handle the sourceopen event on the MediaSource
57531     *
57532     * @private
57533     */
57534    ;
57535
57536    _proto.handleSourceOpen_ = function handleSourceOpen_() {
57537      // Only attempt to create the source buffer if none already exist.
57538      // handleSourceOpen is also called when we are "re-opening" a source buffer
57539      // after `endOfStream` has been called (in response to a seek for instance)
57540      this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
57541      // code in video.js but is required because play() must be invoked
57542      // *after* the media source has opened.
57543
57544      if (this.tech_.autoplay()) {
57545        var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
57546        // on browsers which return a promise
57547
57548        if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
57549          playPromise.then(null, function (e) {});
57550        }
57551      }
57552
57553      this.trigger('sourceopen');
57554    }
57555    /**
57556     * handle the sourceended event on the MediaSource
57557     *
57558     * @private
57559     */
57560    ;
57561
57562    _proto.handleSourceEnded_ = function handleSourceEnded_() {
57563      if (!this.inbandTextTracks_.metadataTrack_) {
57564        return;
57565      }
57566
57567      var cues = this.inbandTextTracks_.metadataTrack_.cues;
57568
57569      if (!cues || !cues.length) {
57570        return;
57571      }
57572
57573      var duration = this.duration();
57574      cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
57575    }
57576    /**
57577     * handle the durationchange event on the MediaSource
57578     *
57579     * @private
57580     */
57581    ;
57582
57583    _proto.handleDurationChange_ = function handleDurationChange_() {
57584      this.tech_.trigger('durationchange');
57585    }
57586    /**
57587     * Calls endOfStream on the media source when all active stream types have called
57588     * endOfStream
57589     *
57590     * @param {string} streamType
57591     *        Stream type of the segment loader that called endOfStream
57592     * @private
57593     */
57594    ;
57595
57596    _proto.onEndOfStream = function onEndOfStream() {
57597      var isEndOfStream = this.mainSegmentLoader_.ended_;
57598
57599      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
57600        // if the audio playlist loader exists, then alternate audio is active
57601        if (!this.mainSegmentLoader_.currentMediaInfo_ || this.mainSegmentLoader_.currentMediaInfo_.hasVideo) {
57602          // if we do not know if the main segment loader contains video yet or if we
57603          // definitively know the main segment loader contains video, then we need to wait
57604          // for both main and audio segment loaders to call endOfStream
57605          isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
57606        } else {
57607          // otherwise just rely on the audio loader
57608          isEndOfStream = this.audioSegmentLoader_.ended_;
57609        }
57610      }
57611
57612      if (!isEndOfStream) {
57613        return;
57614      }
57615
57616      this.sourceUpdater_.endOfStream();
57617    }
57618    /**
57619     * Check if a playlist has stopped being updated
57620     *
57621     * @param {Object} playlist the media playlist object
57622     * @return {boolean} whether the playlist has stopped being updated or not
57623     */
57624    ;
57625
57626    _proto.stuckAtPlaylistEnd_ = function stuckAtPlaylistEnd_(playlist) {
57627      var seekable = this.seekable();
57628
57629      if (!seekable.length) {
57630        // playlist doesn't have enough information to determine whether we are stuck
57631        return false;
57632      }
57633
57634      var expired = this.syncController_.getExpiredTime(playlist, this.duration());
57635
57636      if (expired === null) {
57637        return false;
57638      } // does not use the safe live end to calculate playlist end, since we
57639      // don't want to say we are stuck while there is still content
57640
57641
57642      var absolutePlaylistEnd = Vhs.Playlist.playlistEnd(playlist, expired);
57643      var currentTime = this.tech_.currentTime();
57644      var buffered = this.tech_.buffered();
57645
57646      if (!buffered.length) {
57647        // return true if the playhead reached the absolute end of the playlist
57648        return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
57649      }
57650
57651      var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
57652      // end of playlist
57653
57654      return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
57655    }
57656    /**
57657     * Blacklists a playlist when an error occurs for a set amount of time
57658     * making it unavailable for selection by the rendition selection algorithm
57659     * and then forces a new playlist (rendition) selection.
57660     *
57661     * @param {Object=} error an optional error that may include the playlist
57662     * to blacklist
57663     * @param {number=} blacklistDuration an optional number of seconds to blacklist the
57664     * playlist
57665     */
57666    ;
57667
57668    _proto.blacklistCurrentPlaylist = function blacklistCurrentPlaylist(error, blacklistDuration) {
57669      if (error === void 0) {
57670        error = {};
57671      } // If the `error` was generated by the playlist loader, it will contain
57672      // the playlist we were trying to load (but failed) and that should be
57673      // blacklisted instead of the currently selected playlist which is likely
57674      // out-of-date in this scenario
57675
57676
57677      var currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
57678      blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
57679      // trying to load the master OR while we were disposing of the tech
57680
57681      if (!currentPlaylist) {
57682        this.error = error;
57683
57684        if (this.mediaSource.readyState !== 'open') {
57685          this.trigger('error');
57686        } else {
57687          this.sourceUpdater_.endOfStream('network');
57688        }
57689
57690        return;
57691      }
57692
57693      var playlists = this.masterPlaylistLoader_.master.playlists;
57694      var enabledPlaylists = playlists.filter(isEnabled);
57695      var isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === currentPlaylist; // Don't blacklist the only playlist unless it was blacklisted
57696      // forever
57697
57698      if (playlists.length === 1 && blacklistDuration !== Infinity) {
57699        videojs$1.log.warn("Problem encountered with playlist " + currentPlaylist.id + ". " + 'Trying again since it is the only playlist.');
57700        this.tech_.trigger('retryplaylist');
57701        return this.masterPlaylistLoader_.load(isFinalRendition);
57702      }
57703
57704      if (isFinalRendition) {
57705        // Since we're on the final non-blacklisted playlist, and we're about to blacklist
57706        // it, instead of erring the player or retrying this playlist, clear out the current
57707        // blacklist. This allows other playlists to be attempted in case any have been
57708        // fixed.
57709        var reincluded = false;
57710        playlists.forEach(function (playlist) {
57711          // skip current playlist which is about to be blacklisted
57712          if (playlist === currentPlaylist) {
57713            return;
57714          }
57715
57716          var excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
57717
57718          if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
57719            reincluded = true;
57720            delete playlist.excludeUntil;
57721          }
57722        });
57723
57724        if (reincluded) {
57725          videojs$1.log.warn('Removing other playlists from the exclusion list because the last ' + 'rendition is about to be excluded.'); // Technically we are retrying a playlist, in that we are simply retrying a previous
57726          // playlist. This is needed for users relying on the retryplaylist event to catch a
57727          // case where the player might be stuck and looping through "dead" playlists.
57728
57729          this.tech_.trigger('retryplaylist');
57730        }
57731      } // Blacklist this playlist
57732
57733
57734      currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
57735      this.tech_.trigger('blacklistplaylist');
57736      this.tech_.trigger({
57737        type: 'usage',
57738        name: 'vhs-rendition-blacklisted'
57739      });
57740      this.tech_.trigger({
57741        type: 'usage',
57742        name: 'hls-rendition-blacklisted'
57743      }); // TODO: should we select a new playlist if this blacklist wasn't for the currentPlaylist?
57744      // Would be something like media().id !=== currentPlaylist.id and we  would need something
57745      // like `pendingMedia` in playlist loaders to check against that too. This will prevent us
57746      // from loading a new playlist on any blacklist.
57747      // Select a new playlist
57748
57749      var nextPlaylist = this.selectPlaylist();
57750
57751      if (!nextPlaylist) {
57752        this.error = 'Playback cannot continue. No available working or supported playlists.';
57753        this.trigger('error');
57754        return;
57755      }
57756
57757      var logFn = error.internal ? this.logger_ : videojs$1.log.warn;
57758      var errorMessage = error.message ? ' ' + error.message : '';
57759      logFn((error.internal ? 'Internal problem' : 'Problem') + " encountered with playlist " + currentPlaylist.id + "." + (errorMessage + " Switching to playlist " + nextPlaylist.id + ".")); // if audio group changed reset audio loaders
57760
57761      if (nextPlaylist.attributes.AUDIO !== currentPlaylist.attributes.AUDIO) {
57762        this.delegateLoaders_('audio', ['abort', 'pause']);
57763      } // if subtitle group changed reset subtitle loaders
57764
57765
57766      if (nextPlaylist.attributes.SUBTITLES !== currentPlaylist.attributes.SUBTITLES) {
57767        this.delegateLoaders_('subtitle', ['abort', 'pause']);
57768      }
57769
57770      this.delegateLoaders_('main', ['abort', 'pause']);
57771      return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition);
57772    }
57773    /**
57774     * Pause all segment/playlist loaders
57775     */
57776    ;
57777
57778    _proto.pauseLoading = function pauseLoading() {
57779      this.delegateLoaders_('all', ['abort', 'pause']);
57780    }
57781    /**
57782     * Call a set of functions in order on playlist loaders, segment loaders,
57783     * or both types of loaders.
57784     *
57785     * @param {string} filter
57786     *        Filter loaders that should call fnNames using a string. Can be:
57787     *        * all - run on all loaders
57788     *        * audio - run on all audio loaders
57789     *        * subtitle - run on all subtitle loaders
57790     *        * main - run on the main/master loaders
57791     *
57792     * @param {Array|string} fnNames
57793     *        A string or array of function names to call.
57794     */
57795    ;
57796
57797    _proto.delegateLoaders_ = function delegateLoaders_(filter, fnNames) {
57798      var _this6 = this;
57799
57800      var loaders = [];
57801      var dontFilterPlaylist = filter === 'all';
57802
57803      if (dontFilterPlaylist || filter === 'main') {
57804        loaders.push(this.masterPlaylistLoader_);
57805      }
57806
57807      var mediaTypes = [];
57808
57809      if (dontFilterPlaylist || filter === 'audio') {
57810        mediaTypes.push('AUDIO');
57811      }
57812
57813      if (dontFilterPlaylist || filter === 'subtitle') {
57814        mediaTypes.push('CLOSED-CAPTIONS');
57815        mediaTypes.push('SUBTITLES');
57816      }
57817
57818      mediaTypes.forEach(function (mediaType) {
57819        var loader = _this6.mediaTypes_[mediaType] && _this6.mediaTypes_[mediaType].activePlaylistLoader;
57820
57821        if (loader) {
57822          loaders.push(loader);
57823        }
57824      });
57825      ['main', 'audio', 'subtitle'].forEach(function (name) {
57826        var loader = _this6[name + "SegmentLoader_"];
57827
57828        if (loader && (filter === name || filter === 'all')) {
57829          loaders.push(loader);
57830        }
57831      });
57832      loaders.forEach(function (loader) {
57833        return fnNames.forEach(function (fnName) {
57834          if (typeof loader[fnName] === 'function') {
57835            loader[fnName]();
57836          }
57837        });
57838      });
57839    }
57840    /**
57841     * set the current time on all segment loaders
57842     *
57843     * @param {TimeRange} currentTime the current time to set
57844     * @return {TimeRange} the current time
57845     */
57846    ;
57847
57848    _proto.setCurrentTime = function setCurrentTime(currentTime) {
57849      var buffered = findRange(this.tech_.buffered(), currentTime);
57850
57851      if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
57852        // return immediately if the metadata is not ready yet
57853        return 0;
57854      } // it's clearly an edge-case but don't thrown an error if asked to
57855      // seek within an empty playlist
57856
57857
57858      if (!this.masterPlaylistLoader_.media().segments) {
57859        return 0;
57860      } // if the seek location is already buffered, continue buffering as usual
57861
57862
57863      if (buffered && buffered.length) {
57864        return currentTime;
57865      } // cancel outstanding requests so we begin buffering at the new
57866      // location
57867
57868
57869      this.mainSegmentLoader_.resetEverything();
57870      this.mainSegmentLoader_.abort();
57871
57872      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
57873        this.audioSegmentLoader_.resetEverything();
57874        this.audioSegmentLoader_.abort();
57875      }
57876
57877      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
57878        this.subtitleSegmentLoader_.resetEverything();
57879        this.subtitleSegmentLoader_.abort();
57880      } // start segment loader loading in case they are paused
57881
57882
57883      this.load();
57884    }
57885    /**
57886     * get the current duration
57887     *
57888     * @return {TimeRange} the duration
57889     */
57890    ;
57891
57892    _proto.duration = function duration() {
57893      if (!this.masterPlaylistLoader_) {
57894        return 0;
57895      }
57896
57897      var media = this.masterPlaylistLoader_.media();
57898
57899      if (!media) {
57900        // no playlists loaded yet, so can't determine a duration
57901        return 0;
57902      } // Don't rely on the media source for duration in the case of a live playlist since
57903      // setting the native MediaSource's duration to infinity ends up with consequences to
57904      // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
57905      //
57906      // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
57907      // however, few browsers have support for setLiveSeekableRange()
57908      // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
57909      //
57910      // Until a time when the duration of the media source can be set to infinity, and a
57911      // seekable range specified across browsers, just return Infinity.
57912
57913
57914      if (!media.endList) {
57915        return Infinity;
57916      } // Since this is a VOD video, it is safe to rely on the media source's duration (if
57917      // available). If it's not available, fall back to a playlist-calculated estimate.
57918
57919
57920      if (this.mediaSource) {
57921        return this.mediaSource.duration;
57922      }
57923
57924      return Vhs.Playlist.duration(media);
57925    }
57926    /**
57927     * check the seekable range
57928     *
57929     * @return {TimeRange} the seekable range
57930     */
57931    ;
57932
57933    _proto.seekable = function seekable() {
57934      return this.seekable_;
57935    };
57936
57937    _proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() {
57938      var audioSeekable;
57939
57940      if (!this.masterPlaylistLoader_) {
57941        return;
57942      }
57943
57944      var media = this.masterPlaylistLoader_.media();
57945
57946      if (!media) {
57947        return;
57948      }
57949
57950      var expired = this.syncController_.getExpiredTime(media, this.duration());
57951
57952      if (expired === null) {
57953        // not enough information to update seekable
57954        return;
57955      }
57956
57957      var suggestedPresentationDelay = this.masterPlaylistLoader_.master.suggestedPresentationDelay;
57958      var mainSeekable = Vhs.Playlist.seekable(media, expired, suggestedPresentationDelay);
57959
57960      if (mainSeekable.length === 0) {
57961        return;
57962      }
57963
57964      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
57965        media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
57966        expired = this.syncController_.getExpiredTime(media, this.duration());
57967
57968        if (expired === null) {
57969          return;
57970        }
57971
57972        audioSeekable = Vhs.Playlist.seekable(media, expired, suggestedPresentationDelay);
57973
57974        if (audioSeekable.length === 0) {
57975          return;
57976        }
57977      }
57978
57979      var oldEnd;
57980      var oldStart;
57981
57982      if (this.seekable_ && this.seekable_.length) {
57983        oldEnd = this.seekable_.end(0);
57984        oldStart = this.seekable_.start(0);
57985      }
57986
57987      if (!audioSeekable) {
57988        // seekable has been calculated based on buffering video data so it
57989        // can be returned directly
57990        this.seekable_ = mainSeekable;
57991      } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
57992        // seekables are pretty far off, rely on main
57993        this.seekable_ = mainSeekable;
57994      } else {
57995        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)]]);
57996      } // seekable is the same as last time
57997
57998
57999      if (this.seekable_ && this.seekable_.length) {
58000        if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
58001          return;
58002        }
58003      }
58004
58005      this.logger_("seekable updated [" + printableRange(this.seekable_) + "]");
58006      this.tech_.trigger('seekablechanged');
58007    }
58008    /**
58009     * Update the player duration
58010     */
58011    ;
58012
58013    _proto.updateDuration = function updateDuration(isLive) {
58014      if (this.updateDuration_) {
58015        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
58016        this.updateDuration_ = null;
58017      }
58018
58019      if (this.mediaSource.readyState !== 'open') {
58020        this.updateDuration_ = this.updateDuration.bind(this, isLive);
58021        this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
58022        return;
58023      }
58024
58025      if (isLive) {
58026        var seekable = this.seekable();
58027
58028        if (!seekable.length) {
58029          return;
58030        } // Even in the case of a live playlist, the native MediaSource's duration should not
58031        // be set to Infinity (even though this would be expected for a live playlist), since
58032        // setting the native MediaSource's duration to infinity ends up with consequences to
58033        // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
58034        //
58035        // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
58036        // however, few browsers have support for setLiveSeekableRange()
58037        // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
58038        //
58039        // Until a time when the duration of the media source can be set to infinity, and a
58040        // seekable range specified across browsers, the duration should be greater than or
58041        // equal to the last possible seekable value.
58042        // MediaSource duration starts as NaN
58043        // It is possible (and probable) that this case will never be reached for many
58044        // sources, since the MediaSource reports duration as the highest value without
58045        // accounting for timestamp offset. For example, if the timestamp offset is -100 and
58046        // we buffered times 0 to 100 with real times of 100 to 200, even though current
58047        // time will be between 0 and 100, the native media source may report the duration
58048        // as 200. However, since we report duration separate from the media source (as
58049        // Infinity), and as long as the native media source duration value is greater than
58050        // our reported seekable range, seeks will work as expected. The large number as
58051        // duration for live is actually a strategy used by some players to work around the
58052        // issue of live seekable ranges cited above.
58053
58054
58055        if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
58056          this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
58057        }
58058
58059        return;
58060      }
58061
58062      var buffered = this.tech_.buffered();
58063      var duration = Vhs.Playlist.duration(this.masterPlaylistLoader_.media());
58064
58065      if (buffered.length > 0) {
58066        duration = Math.max(duration, buffered.end(buffered.length - 1));
58067      }
58068
58069      if (this.mediaSource.duration !== duration) {
58070        this.sourceUpdater_.setDuration(duration);
58071      }
58072    }
58073    /**
58074     * dispose of the MasterPlaylistController and everything
58075     * that it controls
58076     */
58077    ;
58078
58079    _proto.dispose = function dispose() {
58080      var _this7 = this;
58081
58082      this.trigger('dispose');
58083      this.decrypter_.terminate();
58084      this.masterPlaylistLoader_.dispose();
58085      this.mainSegmentLoader_.dispose();
58086      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
58087        var groups = _this7.mediaTypes_[type].groups;
58088
58089        for (var id in groups) {
58090          groups[id].forEach(function (group) {
58091            if (group.playlistLoader) {
58092              group.playlistLoader.dispose();
58093            }
58094          });
58095        }
58096      });
58097      this.audioSegmentLoader_.dispose();
58098      this.subtitleSegmentLoader_.dispose();
58099      this.sourceUpdater_.dispose();
58100      this.timelineChangeController_.dispose();
58101
58102      if (this.updateDuration_) {
58103        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
58104      }
58105
58106      this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
58107
58108      this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
58109      this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
58110      this.off();
58111    }
58112    /**
58113     * return the master playlist object if we have one
58114     *
58115     * @return {Object} the master playlist object that we parsed
58116     */
58117    ;
58118
58119    _proto.master = function master() {
58120      return this.masterPlaylistLoader_.master;
58121    }
58122    /**
58123     * return the currently selected playlist
58124     *
58125     * @return {Object} the currently selected playlist object that we parsed
58126     */
58127    ;
58128
58129    _proto.media = function media() {
58130      // playlist loader will not return media if it has not been fully loaded
58131      return this.masterPlaylistLoader_.media() || this.initialMedia_;
58132    };
58133
58134    _proto.areMediaTypesKnown_ = function areMediaTypesKnown_() {
58135      var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; // one or both loaders has not loaded sufficently to get codecs
58136
58137      if (!this.mainSegmentLoader_.currentMediaInfo_ || usingAudioLoader && !this.audioSegmentLoader_.currentMediaInfo_) {
58138        return false;
58139      }
58140
58141      return true;
58142    };
58143
58144    _proto.getCodecsOrExclude_ = function getCodecsOrExclude_() {
58145      var _this8 = this;
58146
58147      var media = {
58148        main: this.mainSegmentLoader_.currentMediaInfo_ || {},
58149        audio: this.audioSegmentLoader_.currentMediaInfo_ || {}
58150      }; // set "main" media equal to video
58151
58152      media.video = media.main;
58153      var playlistCodecs = codecsForPlaylist(this.master(), this.media());
58154      var codecs = {};
58155      var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
58156
58157      if (media.main.hasVideo) {
58158        codecs.video = playlistCodecs.video || media.main.videoCodec || codecs_2;
58159      }
58160
58161      if (media.main.isMuxed) {
58162        codecs.video += "," + (playlistCodecs.audio || media.main.audioCodec || codecs_1);
58163      }
58164
58165      if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
58166        codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || codecs_1; // set audio isFmp4 so we use the correct "supports" function below
58167
58168        media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
58169      } // no codecs, no playback.
58170
58171
58172      if (!codecs.audio && !codecs.video) {
58173        this.blacklistCurrentPlaylist({
58174          playlist: this.media(),
58175          message: 'Could not determine codecs for playlist.',
58176          blacklistDuration: Infinity
58177        });
58178        return;
58179      } // fmp4 relies on browser support, while ts relies on muxer support
58180
58181
58182      var supportFunction = function supportFunction(isFmp4, codec) {
58183        return isFmp4 ? codecs_3(codec) : codecs_9(codec);
58184      };
58185
58186      var unsupportedCodecs = {};
58187      var unsupportedAudio;
58188      ['video', 'audio'].forEach(function (type) {
58189        if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
58190          var supporter = media[type].isFmp4 ? 'browser' : 'muxer';
58191          unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
58192          unsupportedCodecs[supporter].push(codecs[type]);
58193
58194          if (type === 'audio') {
58195            unsupportedAudio = supporter;
58196          }
58197        }
58198      });
58199
58200      if (usingAudioLoader && unsupportedAudio && this.media().attributes.AUDIO) {
58201        var audioGroup = this.media().attributes.AUDIO;
58202        this.master().playlists.forEach(function (variant) {
58203          var variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
58204
58205          if (variantAudioGroup === audioGroup && variant !== _this8.media()) {
58206            variant.excludeUntil = Infinity;
58207          }
58208        });
58209        this.logger_("excluding audio group " + audioGroup + " as " + unsupportedAudio + " does not support codec(s): \"" + codecs.audio + "\"");
58210      } // if we have any unsupported codecs blacklist this playlist.
58211
58212
58213      if (Object.keys(unsupportedCodecs).length) {
58214        var message = Object.keys(unsupportedCodecs).reduce(function (acc, supporter) {
58215          if (acc) {
58216            acc += ', ';
58217          }
58218
58219          acc += supporter + " does not support codec(s): \"" + unsupportedCodecs[supporter].join(',') + "\"";
58220          return acc;
58221        }, '') + '.';
58222        this.blacklistCurrentPlaylist({
58223          playlist: this.media(),
58224          internal: true,
58225          message: message,
58226          blacklistDuration: Infinity
58227        });
58228        return;
58229      } // check if codec switching is happening
58230
58231
58232      if (this.sourceUpdater_.ready() && !this.sourceUpdater_.canChangeType()) {
58233        var switchMessages = [];
58234        ['video', 'audio'].forEach(function (type) {
58235          var newCodec = (codecs_10(_this8.sourceUpdater_.codecs[type] || '')[type] || {}).type;
58236          var oldCodec = (codecs_10(codecs[type] || '')[type] || {}).type;
58237
58238          if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
58239            switchMessages.push("\"" + _this8.sourceUpdater_.codecs[type] + "\" -> \"" + codecs[type] + "\"");
58240          }
58241        });
58242
58243        if (switchMessages.length) {
58244          this.blacklistCurrentPlaylist({
58245            playlist: this.media(),
58246            message: "Codec switching not supported: " + switchMessages.join(', ') + ".",
58247            blacklistDuration: Infinity,
58248            internal: true
58249          });
58250          return;
58251        }
58252      } // TODO: when using the muxer shouldn't we just return
58253      // the codecs that the muxer outputs?
58254
58255
58256      return codecs;
58257    }
58258    /**
58259     * Create source buffers and exlude any incompatible renditions.
58260     *
58261     * @private
58262     */
58263    ;
58264
58265    _proto.tryToCreateSourceBuffers_ = function tryToCreateSourceBuffers_() {
58266      // media source is not ready yet or sourceBuffers are already
58267      // created.
58268      if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.ready()) {
58269        return;
58270      }
58271
58272      if (!this.areMediaTypesKnown_()) {
58273        return;
58274      }
58275
58276      var codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
58277
58278      if (!codecs) {
58279        return;
58280      }
58281
58282      this.sourceUpdater_.createSourceBuffers(codecs);
58283      var codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
58284      this.excludeIncompatibleVariants_(codecString);
58285    }
58286    /**
58287     * Excludes playlists with codecs that are unsupported by the muxer and browser.
58288     */
58289    ;
58290
58291    _proto.excludeUnsupportedVariants_ = function excludeUnsupportedVariants_() {
58292      var _this9 = this;
58293
58294      this.master().playlists.forEach(function (variant) {
58295        var codecs = codecsForPlaylist(_this9.master, variant);
58296
58297        if (codecs.audio && !codecs_9(codecs.audio) && !codecs_3(codecs.audio)) {
58298          variant.excludeUntil = Infinity;
58299        }
58300
58301        if (codecs.video && !codecs_9(codecs.video) && !codecs_3(codecs.video)) {
58302          variant.excludeUntil = Infinity;
58303        }
58304      });
58305    }
58306    /**
58307     * Blacklist playlists that are known to be codec or
58308     * stream-incompatible with the SourceBuffer configuration. For
58309     * instance, Media Source Extensions would cause the video element to
58310     * stall waiting for video data if you switched from a variant with
58311     * video and audio to an audio-only one.
58312     *
58313     * @param {Object} media a media playlist compatible with the current
58314     * set of SourceBuffers. Variants in the current master playlist that
58315     * do not appear to have compatible codec or stream configurations
58316     * will be excluded from the default playlist selection algorithm
58317     * indefinitely.
58318     * @private
58319     */
58320    ;
58321
58322    _proto.excludeIncompatibleVariants_ = function excludeIncompatibleVariants_(codecString) {
58323      var _this10 = this;
58324
58325      var codecs = codecs_10(codecString);
58326      var codecCount = Object.keys(codecs).length;
58327      this.master().playlists.forEach(function (variant) {
58328        // skip variants that are already blacklisted forever
58329        if (variant.excludeUntil === Infinity) {
58330          return;
58331        }
58332        /* TODO: Decide whether two codecs should be assumed here.
58333         * Right now, for playlists that don't specify codecs, VHS assumes
58334         * that there are two (one for audio and one for video).
58335         * Although this is often the case, this may lead to broken behavior
58336         * if the playlist only has one codec. It may be better in the future
58337         * to decide at time of segment download how many tracks there are and
58338         * determine the proper codecs. This will come at a cost of potentially
58339         * more bandwidth, but will be a more robust approach than the assumption here.
58340         */
58341
58342
58343        var variantCodecs = {};
58344        var variantCodecCount = 2;
58345        var blacklistReasons = []; // get codecs from the playlist for this variant
58346
58347        var variantCodecStrings = codecsForPlaylist(_this10.masterPlaylistLoader_.master, variant);
58348
58349        if (variantCodecStrings.audio || variantCodecStrings.video) {
58350          var variantCodecString = [variantCodecStrings.video, variantCodecStrings.audio].filter(Boolean).join(',');
58351          variantCodecs = codecs_10(variantCodecString);
58352          variantCodecCount = Object.keys(variantCodecs).length;
58353        } // TODO: we can support this by removing the
58354        // old media source and creating a new one, but it will take some work.
58355        // The number of streams cannot change
58356
58357
58358        if (variantCodecCount !== codecCount) {
58359          blacklistReasons.push("codec count \"" + variantCodecCount + "\" !== \"" + codecCount + "\"");
58360          variant.excludeUntil = Infinity;
58361        } // only exclude playlists by codec change, if codecs cannot switch
58362        // during playback.
58363
58364
58365        if (!_this10.sourceUpdater_.canChangeType()) {
58366          // the video codec cannot change
58367          if (variantCodecs.video && codecs.video && variantCodecs.video.type.toLowerCase() !== codecs.video.type.toLowerCase()) {
58368            blacklistReasons.push("video codec \"" + variantCodecs.video.type + "\" !== \"" + codecs.video.type + "\"");
58369            variant.excludeUntil = Infinity;
58370          } // the audio codec cannot change
58371
58372
58373          if (variantCodecs.audio && codecs.audio && variantCodecs.audio.type.toLowerCase() !== codecs.audio.type.toLowerCase()) {
58374            variant.excludeUntil = Infinity;
58375            blacklistReasons.push("audio codec \"" + variantCodecs.audio.type + "\" !== \"" + codecs.audio.type + "\"");
58376          }
58377        }
58378
58379        if (blacklistReasons.length) {
58380          _this10.logger_("blacklisting " + variant.id + ": " + blacklistReasons.join(' && '));
58381        }
58382      });
58383    };
58384
58385    _proto.updateAdCues_ = function updateAdCues_(media) {
58386      var offset = 0;
58387      var seekable = this.seekable();
58388
58389      if (seekable.length) {
58390        offset = seekable.start(0);
58391      }
58392
58393      updateAdCues(media, this.cueTagsTrack_, offset);
58394    }
58395    /**
58396     * Calculates the desired forward buffer length based on current time
58397     *
58398     * @return {number} Desired forward buffer length in seconds
58399     */
58400    ;
58401
58402    _proto.goalBufferLength = function goalBufferLength() {
58403      var currentTime = this.tech_.currentTime();
58404      var initial = Config.GOAL_BUFFER_LENGTH;
58405      var rate = Config.GOAL_BUFFER_LENGTH_RATE;
58406      var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
58407      return Math.min(initial + currentTime * rate, max);
58408    }
58409    /**
58410     * Calculates the desired buffer low water line based on current time
58411     *
58412     * @return {number} Desired buffer low water line in seconds
58413     */
58414    ;
58415
58416    _proto.bufferLowWaterLine = function bufferLowWaterLine() {
58417      var currentTime = this.tech_.currentTime();
58418      var initial = Config.BUFFER_LOW_WATER_LINE;
58419      var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
58420      var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
58421      return Math.min(initial + currentTime * rate, max);
58422    };
58423
58424    return MasterPlaylistController;
58425  }(videojs$1.EventTarget);
58426  /**
58427   * Returns a function that acts as the Enable/disable playlist function.
58428   *
58429   * @param {PlaylistLoader} loader - The master playlist loader
58430   * @param {string} playlistID - id of the playlist
58431   * @param {Function} changePlaylistFn - A function to be called after a
58432   * playlist's enabled-state has been changed. Will NOT be called if a
58433   * playlist's enabled-state is unchanged
58434   * @param {boolean=} enable - Value to set the playlist enabled-state to
58435   * or if undefined returns the current enabled-state for the playlist
58436   * @return {Function} Function for setting/getting enabled
58437   */
58438
58439
58440  var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) {
58441    return function (enable) {
58442      var playlist = loader.master.playlists[playlistID];
58443      var incompatible = isIncompatible(playlist);
58444      var currentlyEnabled = isEnabled(playlist);
58445
58446      if (typeof enable === 'undefined') {
58447        return currentlyEnabled;
58448      }
58449
58450      if (enable) {
58451        delete playlist.disabled;
58452      } else {
58453        playlist.disabled = true;
58454      }
58455
58456      if (enable !== currentlyEnabled && !incompatible) {
58457        // Ensure the outside world knows about our changes
58458        changePlaylistFn();
58459
58460        if (enable) {
58461          loader.trigger('renditionenabled');
58462        } else {
58463          loader.trigger('renditiondisabled');
58464        }
58465      }
58466
58467      return enable;
58468    };
58469  };
58470  /**
58471   * The representation object encapsulates the publicly visible information
58472   * in a media playlist along with a setter/getter-type function (enabled)
58473   * for changing the enabled-state of a particular playlist entry
58474   *
58475   * @class Representation
58476   */
58477
58478
58479  var Representation = function Representation(vhsHandler, playlist, id) {
58480    var mpc = vhsHandler.masterPlaylistController_,
58481        smoothQualityChange = vhsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
58482
58483    var changeType = smoothQualityChange ? 'smooth' : 'fast';
58484    var qualityChangeFunction = mpc[changeType + "QualityChange_"].bind(mpc); // some playlist attributes are optional
58485
58486    if (playlist.attributes.RESOLUTION) {
58487      var resolution = playlist.attributes.RESOLUTION;
58488      this.width = resolution.width;
58489      this.height = resolution.height;
58490    }
58491
58492    this.bandwidth = playlist.attributes.BANDWIDTH;
58493    this.codecs = codecsForPlaylist(mpc.master(), playlist);
58494    this.playlist = playlist; // The id is simply the ordinality of the media playlist
58495    // within the master playlist
58496
58497    this.id = id; // Partially-apply the enableFunction to create a playlist-
58498    // specific variant
58499
58500    this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
58501  };
58502  /**
58503   * A mixin function that adds the `representations` api to an instance
58504   * of the VhsHandler class
58505   *
58506   * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
58507   * representation API into
58508   */
58509
58510
58511  var renditionSelectionMixin = function renditionSelectionMixin(vhsHandler) {
58512    var playlists = vhsHandler.playlists; // Add a single API-specific function to the VhsHandler instance
58513
58514    vhsHandler.representations = function () {
58515      if (!playlists || !playlists.master || !playlists.master.playlists) {
58516        return [];
58517      }
58518
58519      return playlists.master.playlists.filter(function (media) {
58520        return !isIncompatible(media);
58521      }).map(function (e, i) {
58522        return new Representation(vhsHandler, e, e.id);
58523      });
58524    };
58525  };
58526  /**
58527   * @file playback-watcher.js
58528   *
58529   * Playback starts, and now my watch begins. It shall not end until my death. I shall
58530   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
58531   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
58532   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
58533   * my life and honor to the Playback Watch, for this Player and all the Players to come.
58534   */
58535
58536
58537  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
58538  /**
58539   * Returns whether or not the current time should be considered close to buffered content,
58540   * taking into consideration whether there's enough buffered content for proper playback.
58541   *
58542   * @param {Object} options
58543   *        Options object
58544   * @param {TimeRange} options.buffered
58545   *        Current buffer
58546   * @param {number} options.targetDuration
58547   *        The active playlist's target duration
58548   * @param {number} options.currentTime
58549   *        The current time of the player
58550   * @return {boolean}
58551   *         Whether the current time should be considered close to the buffer
58552   */
58553
58554  var closeToBufferedContent = function closeToBufferedContent(_ref) {
58555    var buffered = _ref.buffered,
58556        targetDuration = _ref.targetDuration,
58557        currentTime = _ref.currentTime;
58558
58559    if (!buffered.length) {
58560      return false;
vendor: 64,298 bytes, lines 58561-60412
58561    } // At least two to three segments worth of content should be buffered before there's a
58562    // full enough buffer to consider taking any actions.
58563
58564
58565    if (buffered.end(0) - buffered.start(0) < targetDuration * 2) {
58566      return false;
58567    } // It's possible that, on seek, a remove hasn't completed and the buffered range is
58568    // somewhere past the current time. In that event, don't consider the buffered content
58569    // close.
58570
58571
58572    if (currentTime > buffered.start(0)) {
58573      return false;
58574    } // Since target duration generally represents the max (or close to max) duration of a
58575    // segment, if the buffer is within a segment of the current time, the gap probably
58576    // won't be closed, and current time should be considered close to buffered content.
58577
58578
58579    return buffered.start(0) - currentTime < targetDuration;
58580  };
58581  /**
58582   * @class PlaybackWatcher
58583   */
58584
58585
58586  var PlaybackWatcher = /*#__PURE__*/function () {
58587    /**
58588     * Represents an PlaybackWatcher object.
58589     *
58590     * @class
58591     * @param {Object} options an object that includes the tech and settings
58592     */
58593    function PlaybackWatcher(options) {
58594      var _this = this;
58595
58596      this.masterPlaylistController_ = options.masterPlaylistController;
58597      this.tech_ = options.tech;
58598      this.seekable = options.seekable;
58599      this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
58600      this.media = options.media;
58601      this.consecutiveUpdates = 0;
58602      this.lastRecordedTime = null;
58603      this.timer_ = null;
58604      this.checkCurrentTimeTimeout_ = null;
58605      this.logger_ = logger('PlaybackWatcher');
58606      this.logger_('initialize');
58607
58608      var canPlayHandler = function canPlayHandler() {
58609        return _this.monitorCurrentTime_();
58610      };
58611
58612      var waitingHandler = function waitingHandler() {
58613        return _this.techWaiting_();
58614      };
58615
58616      var cancelTimerHandler = function cancelTimerHandler() {
58617        return _this.cancelTimer_();
58618      };
58619
58620      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
58621        return _this.fixesBadSeeks_();
58622      };
58623
58624      var mpc = this.masterPlaylistController_;
58625      var loaderTypes = ['main', 'subtitle', 'audio'];
58626      var loaderChecks = {};
58627      loaderTypes.forEach(function (type) {
58628        loaderChecks[type] = {
58629          reset: function reset() {
58630            return _this.resetSegmentDownloads_(type);
58631          },
58632          updateend: function updateend() {
58633            return _this.checkSegmentDownloads_(type);
58634          }
58635        };
58636        mpc[type + "SegmentLoader_"].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
58637        // isn't changing we want to reset. We cannot assume that the new rendition
58638        // will also be stalled, until after new appends.
58639
58640        mpc[type + "SegmentLoader_"].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
58641        // This prevents one segment playlists (single vtt or single segment content)
58642        // from being detected as stalling. As the buffer will not change in those cases, since
58643        // the buffer is the entire video duration.
58644
58645        _this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
58646      });
58647      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
58648      this.tech_.on('waiting', waitingHandler);
58649      this.tech_.on(timerCancelEvents, cancelTimerHandler);
58650      this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
58651
58652      this.dispose = function () {
58653        _this.logger_('dispose');
58654
58655        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
58656
58657        _this.tech_.off('waiting', waitingHandler);
58658
58659        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
58660
58661        _this.tech_.off('canplay', canPlayHandler);
58662
58663        loaderTypes.forEach(function (type) {
58664          mpc[type + "SegmentLoader_"].off('appendsdone', loaderChecks[type].updateend);
58665          mpc[type + "SegmentLoader_"].off('playlistupdate', loaderChecks[type].reset);
58666
58667          _this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
58668        });
58669
58670        if (_this.checkCurrentTimeTimeout_) {
58671          window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
58672        }
58673
58674        _this.cancelTimer_();
58675      };
58676    }
58677    /**
58678     * Periodically check current time to see if playback stopped
58679     *
58680     * @private
58681     */
58682
58683
58684    var _proto = PlaybackWatcher.prototype;
58685
58686    _proto.monitorCurrentTime_ = function monitorCurrentTime_() {
58687      this.checkCurrentTime_();
58688
58689      if (this.checkCurrentTimeTimeout_) {
58690        window$1.clearTimeout(this.checkCurrentTimeTimeout_);
58691      } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
58692
58693
58694      this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
58695    }
58696    /**
58697     * Reset stalled download stats for a specific type of loader
58698     *
58699     * @param {string} type
58700     *        The segment loader type to check.
58701     *
58702     * @listens SegmentLoader#playlistupdate
58703     * @listens Tech#seeking
58704     * @listens Tech#seeked
58705     */
58706    ;
58707
58708    _proto.resetSegmentDownloads_ = function resetSegmentDownloads_(type) {
58709      var loader = this.masterPlaylistController_[type + "SegmentLoader_"];
58710
58711      if (this[type + "StalledDownloads_"] > 0) {
58712        this.logger_("resetting possible stalled download count for " + type + " loader");
58713      }
58714
58715      this[type + "StalledDownloads_"] = 0;
58716      this[type + "Buffered_"] = loader.buffered_();
58717    }
58718    /**
58719     * Checks on every segment `appendsdone` to see
58720     * if segment appends are making progress. If they are not
58721     * and we are still downloading bytes. We blacklist the playlist.
58722     *
58723     * @param {string} type
58724     *        The segment loader type to check.
58725     *
58726     * @listens SegmentLoader#appendsdone
58727     */
58728    ;
58729
58730    _proto.checkSegmentDownloads_ = function checkSegmentDownloads_(type) {
58731      var mpc = this.masterPlaylistController_;
58732      var loader = mpc[type + "SegmentLoader_"];
58733      var buffered = loader.buffered_();
58734      var isBufferedDifferent = isRangeDifferent(this[type + "Buffered_"], buffered);
58735      this[type + "Buffered_"] = buffered; // if another watcher is going to fix the issue or
58736      // the buffered value for this loader changed
58737      // appends are working
58738
58739      if (isBufferedDifferent) {
58740        this.resetSegmentDownloads_(type);
58741        return;
58742      }
58743
58744      this[type + "StalledDownloads_"]++;
58745      this.logger_("found #" + this[type + "StalledDownloads_"] + " " + type + " appends that did not increase buffer (possible stalled download)", {
58746        playlistId: loader.playlist_ && loader.playlist_.id,
58747        buffered: timeRangesToArray(buffered)
58748      }); // after 10 possibly stalled appends with no reset, exclude
58749
58750      if (this[type + "StalledDownloads_"] < 10) {
58751        return;
58752      }
58753
58754      this.logger_(type + " loader stalled download exclusion");
58755      this.resetSegmentDownloads_(type);
58756      this.tech_.trigger({
58757        type: 'usage',
58758        name: "vhs-" + type + "-download-exclusion"
58759      });
58760
58761      if (type === 'subtitle') {
58762        return;
58763      } // TODO: should we exclude audio tracks rather than main tracks
58764      // when type is audio?
58765
58766
58767      mpc.blacklistCurrentPlaylist({
58768        message: "Excessive " + type + " segment downloading detected."
58769      }, Infinity);
58770    }
58771    /**
58772     * The purpose of this function is to emulate the "waiting" event on
58773     * browsers that do not emit it when they are waiting for more
58774     * data to continue playback
58775     *
58776     * @private
58777     */
58778    ;
58779
58780    _proto.checkCurrentTime_ = function checkCurrentTime_() {
58781      if (this.tech_.seeking() && this.fixesBadSeeks_()) {
58782        this.consecutiveUpdates = 0;
58783        this.lastRecordedTime = this.tech_.currentTime();
58784        return;
58785      }
58786
58787      if (this.tech_.paused() || this.tech_.seeking()) {
58788        return;
58789      }
58790
58791      var currentTime = this.tech_.currentTime();
58792      var buffered = this.tech_.buffered();
58793
58794      if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
58795        // If current time is at the end of the final buffered region, then any playback
58796        // stall is most likely caused by buffering in a low bandwidth environment. The tech
58797        // should fire a `waiting` event in this scenario, but due to browser and tech
58798        // inconsistencies. Calling `techWaiting_` here allows us to simulate
58799        // responding to a native `waiting` event when the tech fails to emit one.
58800        return this.techWaiting_();
58801      }
58802
58803      if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
58804        this.consecutiveUpdates++;
58805        this.waiting_();
58806      } else if (currentTime === this.lastRecordedTime) {
58807        this.consecutiveUpdates++;
58808      } else {
58809        this.consecutiveUpdates = 0;
58810        this.lastRecordedTime = currentTime;
58811      }
58812    }
58813    /**
58814     * Cancels any pending timers and resets the 'timeupdate' mechanism
58815     * designed to detect that we are stalled
58816     *
58817     * @private
58818     */
58819    ;
58820
58821    _proto.cancelTimer_ = function cancelTimer_() {
58822      this.consecutiveUpdates = 0;
58823
58824      if (this.timer_) {
58825        this.logger_('cancelTimer_');
58826        clearTimeout(this.timer_);
58827      }
58828
58829      this.timer_ = null;
58830    }
58831    /**
58832     * Fixes situations where there's a bad seek
58833     *
58834     * @return {boolean} whether an action was taken to fix the seek
58835     * @private
58836     */
58837    ;
58838
58839    _proto.fixesBadSeeks_ = function fixesBadSeeks_() {
58840      var seeking = this.tech_.seeking();
58841
58842      if (!seeking) {
58843        return false;
58844      }
58845
58846      var seekable = this.seekable();
58847      var currentTime = this.tech_.currentTime();
58848      var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
58849      var seekTo;
58850
58851      if (isAfterSeekableRange) {
58852        var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
58853
58854        seekTo = seekableEnd;
58855      }
58856
58857      if (this.beforeSeekableWindow_(seekable, currentTime)) {
58858        var seekableStart = seekable.start(0); // sync to the beginning of the live window
58859        // provide a buffer of .1 seconds to handle rounding/imprecise numbers
58860
58861        seekTo = seekableStart + ( // if the playlist is too short and the seekable range is an exact time (can
58862        // happen in live with a 3 segment playlist), then don't use a time delta
58863        seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
58864      }
58865
58866      if (typeof seekTo !== 'undefined') {
58867        this.logger_("Trying to seek outside of seekable at time " + currentTime + " with " + ("seekable range " + printableRange(seekable) + ". Seeking to ") + (seekTo + "."));
58868        this.tech_.setCurrentTime(seekTo);
58869        return true;
58870      }
58871
58872      var buffered = this.tech_.buffered();
58873
58874      if (closeToBufferedContent({
58875        buffered: buffered,
58876        targetDuration: this.media().targetDuration,
58877        currentTime: currentTime
58878      })) {
58879        seekTo = buffered.start(0) + SAFE_TIME_DELTA;
58880        this.logger_("Buffered region starts (" + buffered.start(0) + ") " + (" just beyond seek point (" + currentTime + "). Seeking to " + seekTo + "."));
58881        this.tech_.setCurrentTime(seekTo);
58882        return true;
58883      }
58884
58885      return false;
58886    }
58887    /**
58888     * Handler for situations when we determine the player is waiting.
58889     *
58890     * @private
58891     */
58892    ;
58893
58894    _proto.waiting_ = function waiting_() {
58895      if (this.techWaiting_()) {
58896        return;
58897      } // All tech waiting checks failed. Use last resort correction
58898
58899
58900      var currentTime = this.tech_.currentTime();
58901      var buffered = this.tech_.buffered();
58902      var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
58903      // region with no indication that anything is amiss (seen in Firefox). Seeking to
58904      // currentTime is usually enough to kickstart the player. This checks that the player
58905      // is currently within a buffered region before attempting a corrective seek.
58906      // Chrome does not appear to continue `timeupdate` events after a `waiting` event
58907      // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
58908      // make sure there is ~3 seconds of forward buffer before taking any corrective action
58909      // to avoid triggering an `unknownwaiting` event when the network is slow.
58910
58911      if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
58912        this.cancelTimer_();
58913        this.tech_.setCurrentTime(currentTime);
58914        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.'); // unknown waiting corrections may be useful for monitoring QoS
58915
58916        this.tech_.trigger({
58917          type: 'usage',
58918          name: 'vhs-unknown-waiting'
58919        });
58920        this.tech_.trigger({
58921          type: 'usage',
58922          name: 'hls-unknown-waiting'
58923        });
58924        return;
58925      }
58926    }
58927    /**
58928     * Handler for situations when the tech fires a `waiting` event
58929     *
58930     * @return {boolean}
58931     *         True if an action (or none) was needed to correct the waiting. False if no
58932     *         checks passed
58933     * @private
58934     */
58935    ;
58936
58937    _proto.techWaiting_ = function techWaiting_() {
58938      var seekable = this.seekable();
58939      var currentTime = this.tech_.currentTime();
58940
58941      if (this.tech_.seeking() && this.fixesBadSeeks_()) {
58942        // Tech is seeking or bad seek fixed, no action needed
58943        return true;
58944      }
58945
58946      if (this.tech_.seeking() || this.timer_ !== null) {
58947        // Tech is seeking or already waiting on another action, no action needed
58948        return true;
58949      }
58950
58951      if (this.beforeSeekableWindow_(seekable, currentTime)) {
58952        var livePoint = seekable.end(seekable.length - 1);
58953        this.logger_("Fell out of live window at time " + currentTime + ". Seeking to " + ("live point (seekable end) " + livePoint));
58954        this.cancelTimer_();
58955        this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
58956
58957        this.tech_.trigger({
58958          type: 'usage',
58959          name: 'vhs-live-resync'
58960        });
58961        this.tech_.trigger({
58962          type: 'usage',
58963          name: 'hls-live-resync'
58964        });
58965        return true;
58966      }
58967
58968      var sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_;
58969      var buffered = this.tech_.buffered();
58970      var videoUnderflow = this.videoUnderflow_({
58971        audioBuffered: sourceUpdater.audioBuffered(),
58972        videoBuffered: sourceUpdater.videoBuffered(),
58973        currentTime: currentTime
58974      });
58975
58976      if (videoUnderflow) {
58977        // Even though the video underflowed and was stuck in a gap, the audio overplayed
58978        // the gap, leading currentTime into a buffered range. Seeking to currentTime
58979        // allows the video to catch up to the audio position without losing any audio
58980        // (only suffering ~3 seconds of frozen video and a pause in audio playback).
58981        this.cancelTimer_();
58982        this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
58983
58984        this.tech_.trigger({
58985          type: 'usage',
58986          name: 'vhs-video-underflow'
58987        });
58988        this.tech_.trigger({
58989          type: 'usage',
58990          name: 'hls-video-underflow'
58991        });
58992        return true;
58993      }
58994
58995      var nextRange = findNextRange(buffered, currentTime); // check for gap
58996
58997      if (nextRange.length > 0) {
58998        var difference = nextRange.start(0) - currentTime;
58999        this.logger_("Stopped at " + currentTime + ", setting timer for " + difference + ", seeking " + ("to " + nextRange.start(0)));
59000        this.cancelTimer_();
59001        this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
59002        return true;
59003      } // All checks failed. Returning false to indicate failure to correct waiting
59004
59005
59006      return false;
59007    };
59008
59009    _proto.afterSeekableWindow_ = function afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow) {
59010      if (allowSeeksWithinUnsafeLiveWindow === void 0) {
59011        allowSeeksWithinUnsafeLiveWindow = false;
59012      }
59013
59014      if (!seekable.length) {
59015        // we can't make a solid case if there's no seekable, default to false
59016        return false;
59017      }
59018
59019      var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
59020      var isLive = !playlist.endList;
59021
59022      if (isLive && allowSeeksWithinUnsafeLiveWindow) {
59023        allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
59024      }
59025
59026      if (currentTime > allowedEnd) {
59027        return true;
59028      }
59029
59030      return false;
59031    };
59032
59033    _proto.beforeSeekableWindow_ = function beforeSeekableWindow_(seekable, currentTime) {
59034      if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
59035      seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
59036        return true;
59037      }
59038
59039      return false;
59040    };
59041
59042    _proto.videoUnderflow_ = function videoUnderflow_(_ref2) {
59043      var videoBuffered = _ref2.videoBuffered,
59044          audioBuffered = _ref2.audioBuffered,
59045          currentTime = _ref2.currentTime; // audio only content will not have video underflow :)
59046
59047      if (!videoBuffered) {
59048        return;
59049      }
59050
59051      var gap; // find a gap in demuxed content.
59052
59053      if (videoBuffered.length && audioBuffered.length) {
59054        // in Chrome audio will continue to play for ~3s when we run out of video
59055        // so we have to check that the video buffer did have some buffer in the
59056        // past.
59057        var lastVideoRange = findRange(videoBuffered, currentTime - 3);
59058        var videoRange = findRange(videoBuffered, currentTime);
59059        var audioRange = findRange(audioBuffered, currentTime);
59060
59061        if (audioRange.length && !videoRange.length && lastVideoRange.length) {
59062          gap = {
59063            start: lastVideoRange.end(0),
59064            end: audioRange.end(0)
59065          };
59066        } // find a gap in muxed content.
59067
59068      } else {
59069        var nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
59070        // stuck in a gap due to video underflow.
59071
59072        if (!nextRange.length) {
59073          gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
59074        }
59075      }
59076
59077      if (gap) {
59078        this.logger_("Encountered a gap in video from " + gap.start + " to " + gap.end + ". " + ("Seeking to current time " + currentTime));
59079        return true;
59080      }
59081
59082      return false;
59083    }
59084    /**
59085     * Timer callback. If playback still has not proceeded, then we seek
59086     * to the start of the next buffered region.
59087     *
59088     * @private
59089     */
59090    ;
59091
59092    _proto.skipTheGap_ = function skipTheGap_(scheduledCurrentTime) {
59093      var buffered = this.tech_.buffered();
59094      var currentTime = this.tech_.currentTime();
59095      var nextRange = findNextRange(buffered, currentTime);
59096      this.cancelTimer_();
59097
59098      if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
59099        return;
59100      }
59101
59102      this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
59103
59104      this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
59105      this.tech_.trigger({
59106        type: 'usage',
59107        name: 'vhs-gap-skip'
59108      });
59109      this.tech_.trigger({
59110        type: 'usage',
59111        name: 'hls-gap-skip'
59112      });
59113    };
59114
59115    _proto.gapFromVideoUnderflow_ = function gapFromVideoUnderflow_(buffered, currentTime) {
59116      // At least in Chrome, if there is a gap in the video buffer, the audio will continue
59117      // playing for ~3 seconds after the video gap starts. This is done to account for
59118      // video buffer underflow/underrun (note that this is not done when there is audio
59119      // buffer underflow/underrun -- in that case the video will stop as soon as it
59120      // encounters the gap, as audio stalls are more noticeable/jarring to a user than
59121      // video stalls). The player's time will reflect the playthrough of audio, so the
59122      // time will appear as if we are in a buffered region, even if we are stuck in a
59123      // "gap."
59124      //
59125      // Example:
59126      // video buffer:   0 => 10.1, 10.2 => 20
59127      // audio buffer:   0 => 20
59128      // overall buffer: 0 => 10.1, 10.2 => 20
59129      // current time: 13
59130      //
59131      // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
59132      // however, the audio continued playing until it reached ~3 seconds past the gap
59133      // (13 seconds), at which point it stops as well. Since current time is past the
59134      // gap, findNextRange will return no ranges.
59135      //
59136      // To check for this issue, we see if there is a gap that starts somewhere within
59137      // a 3 second range (3 seconds +/- 1 second) back from our current time.
59138      var gaps = findGaps(buffered);
59139
59140      for (var i = 0; i < gaps.length; i++) {
59141        var start = gaps.start(i);
59142        var end = gaps.end(i); // gap is starts no more than 4 seconds back
59143
59144        if (currentTime - start < 4 && currentTime - start > 2) {
59145          return {
59146            start: start,
59147            end: end
59148          };
59149        }
59150      }
59151
59152      return null;
59153    };
59154
59155    return PlaybackWatcher;
59156  }();
59157
59158  var defaultOptions = {
59159    errorInterval: 30,
59160    getSource: function getSource(next) {
59161      var tech = this.tech({
59162        IWillNotUseThisInPlugins: true
59163      });
59164      var sourceObj = tech.currentSource_ || this.currentSource();
59165      return next(sourceObj);
59166    }
59167  };
59168  /**
59169   * Main entry point for the plugin
59170   *
59171   * @param {Player} player a reference to a videojs Player instance
59172   * @param {Object} [options] an object with plugin options
59173   * @private
59174   */
59175
59176  var initPlugin = function initPlugin(player, options) {
59177    var lastCalled = 0;
59178    var seekTo = 0;
59179    var localOptions = videojs$1.mergeOptions(defaultOptions, options);
59180    player.ready(function () {
59181      player.trigger({
59182        type: 'usage',
59183        name: 'vhs-error-reload-initialized'
59184      });
59185      player.trigger({
59186        type: 'usage',
59187        name: 'hls-error-reload-initialized'
59188      });
59189    });
59190    /**
59191     * Player modifications to perform that must wait until `loadedmetadata`
59192     * has been triggered
59193     *
59194     * @private
59195     */
59196
59197    var loadedMetadataHandler = function loadedMetadataHandler() {
59198      if (seekTo) {
59199        player.currentTime(seekTo);
59200      }
59201    };
59202    /**
59203     * Set the source on the player element, play, and seek if necessary
59204     *
59205     * @param {Object} sourceObj An object specifying the source url and mime-type to play
59206     * @private
59207     */
59208
59209
59210    var setSource = function setSource(sourceObj) {
59211      if (sourceObj === null || sourceObj === undefined) {
59212        return;
59213      }
59214
59215      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
59216      player.one('loadedmetadata', loadedMetadataHandler);
59217      player.src(sourceObj);
59218      player.trigger({
59219        type: 'usage',
59220        name: 'vhs-error-reload'
59221      });
59222      player.trigger({
59223        type: 'usage',
59224        name: 'hls-error-reload'
59225      });
59226      player.play();
59227    };
59228    /**
59229     * Attempt to get a source from either the built-in getSource function
59230     * or a custom function provided via the options
59231     *
59232     * @private
59233     */
59234
59235
59236    var errorHandler = function errorHandler() {
59237      // Do not attempt to reload the source if a source-reload occurred before
59238      // 'errorInterval' time has elapsed since the last source-reload
59239      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
59240        player.trigger({
59241          type: 'usage',
59242          name: 'vhs-error-reload-canceled'
59243        });
59244        player.trigger({
59245          type: 'usage',
59246          name: 'hls-error-reload-canceled'
59247        });
59248        return;
59249      }
59250
59251      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
59252        videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
59253        return;
59254      }
59255
59256      lastCalled = Date.now();
59257      return localOptions.getSource.call(player, setSource);
59258    };
59259    /**
59260     * Unbind any event handlers that were bound by the plugin
59261     *
59262     * @private
59263     */
59264
59265
59266    var cleanupEvents = function cleanupEvents() {
59267      player.off('loadedmetadata', loadedMetadataHandler);
59268      player.off('error', errorHandler);
59269      player.off('dispose', cleanupEvents);
59270    };
59271    /**
59272     * Cleanup before re-initializing the plugin
59273     *
59274     * @param {Object} [newOptions] an object with plugin options
59275     * @private
59276     */
59277
59278
59279    var reinitPlugin = function reinitPlugin(newOptions) {
59280      cleanupEvents();
59281      initPlugin(player, newOptions);
59282    };
59283
59284    player.on('error', errorHandler);
59285    player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
59286    // initializing the plugin
59287
59288    player.reloadSourceOnError = reinitPlugin;
59289  };
59290  /**
59291   * Reload the source when an error is detected as long as there
59292   * wasn't an error previously within the last 30 seconds
59293   *
59294   * @param {Object} [options] an object with plugin options
59295   */
59296
59297
59298  var reloadSourceOnError = function reloadSourceOnError(options) {
59299    initPlugin(this, options);
59300  }; // keep these at the bottom they are replaced at build time
59301  // because webpack and rollup without plugins do not support json
59302  // and we do not want to break our users
59303
59304
59305  var vhsVersion = '2.2.4';
59306  var muxVersion = '5.6.7';
59307  var mpdVersion = '0.14.0';
59308  var m3u8Version = '4.5.0';
59309  var aesVersion = '3.1.0';
59310  var Vhs$1 = {
59311    PlaylistLoader: PlaylistLoader,
59312    Playlist: Playlist,
59313    utils: utils$1,
59314    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
59315    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
59316    comparePlaylistBandwidth: comparePlaylistBandwidth,
59317    comparePlaylistResolution: comparePlaylistResolution,
59318    xhr: xhrFactory()
59319  }; // Define getter/setters for config properties
59320
59321  ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'BACK_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) {
59322    Object.defineProperty(Vhs$1, prop, {
59323      get: function get() {
59324        videojs$1.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing");
59325        return Config[prop];
59326      },
59327      set: function set(value) {
59328        videojs$1.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing");
59329
59330        if (typeof value !== 'number' || value < 0) {
59331          videojs$1.log.warn("value of Vhs." + prop + " must be greater than or equal to 0");
59332          return;
59333        }
59334
59335        Config[prop] = value;
59336      }
59337    });
59338  });
59339  var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
59340  /**
59341   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
59342   *
59343   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
59344   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
59345   * @function handleVhsMediaChange
59346   */
59347
59348  var handleVhsMediaChange = function handleVhsMediaChange(qualityLevels, playlistLoader) {
59349    var newPlaylist = playlistLoader.media();
59350    var selectedIndex = -1;
59351
59352    for (var i = 0; i < qualityLevels.length; i++) {
59353      if (qualityLevels[i].id === newPlaylist.id) {
59354        selectedIndex = i;
59355        break;
59356      }
59357    }
59358
59359    qualityLevels.selectedIndex_ = selectedIndex;
59360    qualityLevels.trigger({
59361      selectedIndex: selectedIndex,
59362      type: 'change'
59363    });
59364  };
59365  /**
59366   * Adds quality levels to list once playlist metadata is available
59367   *
59368   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
59369   * @param {Object} vhs Vhs object to listen to for media events.
59370   * @function handleVhsLoadedMetadata
59371   */
59372
59373
59374  var handleVhsLoadedMetadata = function handleVhsLoadedMetadata(qualityLevels, vhs) {
59375    vhs.representations().forEach(function (rep) {
59376      qualityLevels.addQualityLevel(rep);
59377    });
59378    handleVhsMediaChange(qualityLevels, vhs.playlists);
59379  }; // HLS is a source handler, not a tech. Make sure attempts to use it
59380  // as one do not cause exceptions.
59381
59382
59383  Vhs$1.canPlaySource = function () {
59384    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
59385  };
59386
59387  var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
59388    if (!keySystemOptions) {
59389      return keySystemOptions;
59390    }
59391
59392    var codecs = {
59393      video: videoPlaylist && videoPlaylist.attributes && videoPlaylist.attributes.CODECS,
59394      audio: audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS
59395    };
59396
59397    if (!codecs.audio && codecs.video && codecs.video.split(',').length > 1) {
59398      codecs.video.split(',').forEach(function (codec) {
59399        codec = codec.trim();
59400
59401        if (codecs_6(codec)) {
59402          codecs.audio = codec;
59403        } else if (codecs_7(codec)) {
59404          codecs.video = codec;
59405        }
59406      });
59407    }
59408
59409    var videoContentType = codecs.video ? "video/mp4;codecs=\"" + codecs.video + "\"" : null;
59410    var audioContentType = codecs.audio ? "audio/mp4;codecs=\"" + codecs.audio + "\"" : null; // upsert the content types based on the selected playlist
59411
59412    var keySystemContentTypes = {};
59413
59414    for (var keySystem in keySystemOptions) {
59415      keySystemContentTypes[keySystem] = {
59416        audioContentType: audioContentType,
59417        videoContentType: videoContentType
59418      };
59419
59420      if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
59421        keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
59422      } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
59423      // so we need to prevent overwriting the URL entirely
59424
59425
59426      if (typeof keySystemOptions[keySystem] === 'string') {
59427        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
59428      }
59429    }
59430
59431    return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
59432  };
59433  /**
59434   * @typedef {Object} KeySystems
59435   *
59436   * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
59437   * Note: not all options are listed here.
59438   *
59439   * @property {Uint8Array} [pssh]
59440   *           Protection System Specific Header
59441   */
59442
59443  /**
59444   * Goes through all the playlists and collects an array of KeySystems options objects
59445   * containing each playlist's keySystems and their pssh values, if available.
59446   *
59447   * @param {Object[]} playlists
59448   *        The playlists to look through
59449   * @param {string[]} keySystems
59450   *        The keySystems to collect pssh values for
59451   *
59452   * @return {KeySystems[]}
59453   *         An array of KeySystems objects containing available key systems and their
59454   *         pssh values
59455   */
59456
59457
59458  var getAllPsshKeySystemsOptions = function getAllPsshKeySystemsOptions(playlists, keySystems) {
59459    return playlists.reduce(function (keySystemsArr, playlist) {
59460      if (!playlist.contentProtection) {
59461        return keySystemsArr;
59462      }
59463
59464      var keySystemsOptions = keySystems.reduce(function (keySystemsObj, keySystem) {
59465        var keySystemOptions = playlist.contentProtection[keySystem];
59466
59467        if (keySystemOptions && keySystemOptions.pssh) {
59468          keySystemsObj[keySystem] = {
59469            pssh: keySystemOptions.pssh
59470          };
59471        }
59472
59473        return keySystemsObj;
59474      }, {});
59475
59476      if (Object.keys(keySystemsOptions).length) {
59477        keySystemsArr.push(keySystemsOptions);
59478      }
59479
59480      return keySystemsArr;
59481    }, []);
59482  };
59483  /**
59484   * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
59485   * there are keySystems on the source, sets up source options to prepare the source for
59486   * eme and tries to initialize it early via eme's initializeMediaKeys API (if available).
59487   *
59488   * @param {Object} player
59489   *        The player instance
59490   * @param {Object[]} sourceKeySystems
59491   *        The key systems options from the player source
59492   * @param {Object} media
59493   *        The active media playlist
59494   * @param {Object} [audioMedia]
59495   *        The active audio media playlist (optional)
59496   * @param {Object[]} mainPlaylists
59497   *        The playlists found on the master playlist object
59498   */
59499
59500
59501  var setupEmeOptions = function setupEmeOptions(_ref) {
59502    var player = _ref.player,
59503        sourceKeySystems = _ref.sourceKeySystems,
59504        media = _ref.media,
59505        audioMedia = _ref.audioMedia,
59506        mainPlaylists = _ref.mainPlaylists;
59507    var sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
59508
59509    if (!sourceOptions) {
59510      return;
59511    }
59512
59513    player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
59514    // do nothing.
59515
59516    if (sourceOptions && !player.eme) {
59517      videojs$1.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
59518      return;
59519    } // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
59520    // in non-IE11 browsers. In IE11 this is too early to initialize media keys
59521
59522
59523    if (videojs$1.browser.IE_VERSION === 11 || !player.eme.initializeMediaKeys) {
59524      return;
59525    } // TODO should all audio PSSH values be initialized for DRM?
59526    //
59527    // All unique video rendition pssh values are initialized for DRM, but here only
59528    // the initial audio playlist license is initialized. In theory, an encrypted
59529    // event should be fired if the user switches to an alternative audio playlist
59530    // where a license is required, but this case hasn't yet been tested. In addition, there
59531    // may be many alternate audio playlists unlikely to be used (e.g., multiple different
59532    // languages).
59533
59534
59535    var playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
59536    var keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems)); // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
59537    // only place where it should not be deduped is for ms-prefixed APIs, but the early
59538    // return for IE11 above, and the existence of modern EME APIs in addition to
59539    // ms-prefixed APIs on Edge should prevent this from being a concern.
59540    // initializeMediaKeys also won't use the webkit-prefixed APIs.
59541
59542    keySystemsOptionsArr.forEach(function (keySystemsOptions) {
59543      player.eme.initializeMediaKeys({
59544        keySystems: keySystemsOptions
59545      });
59546    });
59547  };
59548
59549  var getVhsLocalStorage = function getVhsLocalStorage() {
59550    if (!window$1.localStorage) {
59551      return null;
59552    }
59553
59554    var storedObject = window$1.localStorage.getItem(LOCAL_STORAGE_KEY$1);
59555
59556    if (!storedObject) {
59557      return null;
59558    }
59559
59560    try {
59561      return JSON.parse(storedObject);
59562    } catch (e) {
59563      // someone may have tampered with the value
59564      return null;
59565    }
59566  };
59567
59568  var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
59569    if (!window$1.localStorage) {
59570      return false;
59571    }
59572
59573    var objectToStore = getVhsLocalStorage();
59574    objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
59575
59576    try {
59577      window$1.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
59578    } catch (e) {
59579      // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
59580      // storage is set to 0).
59581      // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
59582      // No need to perform any operation.
59583      return false;
59584    }
59585
59586    return objectToStore;
59587  };
59588  /**
59589   * Parses VHS-supported media types from data URIs. See
59590   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
59591   * for information on data URIs.
59592   *
59593   * @param {string} dataUri
59594   *        The data URI
59595   *
59596   * @return {string|Object}
59597   *         The parsed object/string, or the original string if no supported media type
59598   *         was found
59599   */
59600
59601
59602  var expandDataUri = function expandDataUri(dataUri) {
59603    if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
59604      return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
59605    } // no known case for this data URI, return the string as-is
59606
59607
59608    return dataUri;
59609  };
59610  /**
59611   * Whether the browser has built-in HLS support.
59612   */
59613
59614
59615  Vhs$1.supportsNativeHls = function () {
59616    if (!document || !document.createElement) {
59617      return false;
59618    }
59619
59620    var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
59621
59622    if (!videojs$1.getTech('Html5').isSupported()) {
59623      return false;
59624    } // HLS manifests can go by many mime-types
59625
59626
59627    var canPlay = [// Apple santioned
59628    'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
59629    'audio/mpegurl', // Very common
59630    'audio/x-mpegurl', // Very common
59631    'application/x-mpegurl', // Included for completeness
59632    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
59633    return canPlay.some(function (canItPlay) {
59634      return /maybe|probably/i.test(video.canPlayType(canItPlay));
59635    });
59636  }();
59637
59638  Vhs$1.supportsNativeDash = function () {
59639    if (!document || !document.createElement || !videojs$1.getTech('Html5').isSupported()) {
59640      return false;
59641    }
59642
59643    return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
59644  }();
59645
59646  Vhs$1.supportsTypeNatively = function (type) {
59647    if (type === 'hls') {
59648      return Vhs$1.supportsNativeHls;
59649    }
59650
59651    if (type === 'dash') {
59652      return Vhs$1.supportsNativeDash;
59653    }
59654
59655    return false;
59656  };
59657  /**
59658   * HLS is a source handler, not a tech. Make sure attempts to use it
59659   * as one do not cause exceptions.
59660   */
59661
59662
59663  Vhs$1.isSupported = function () {
59664    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
59665  };
59666
59667  var Component$1 = videojs$1.getComponent('Component');
59668  /**
59669   * The Vhs Handler object, where we orchestrate all of the parts
59670   * of HLS to interact with video.js
59671   *
59672   * @class VhsHandler
59673   * @extends videojs.Component
59674   * @param {Object} source the soruce object
59675   * @param {Tech} tech the parent tech object
59676   * @param {Object} options optional and required options
59677   */
59678
59679  var VhsHandler = /*#__PURE__*/function (_Component) {
59680    inheritsLoose(VhsHandler, _Component);
59681
59682    function VhsHandler(source, tech, options) {
59683      var _this;
59684
59685      _this = _Component.call(this, tech, videojs$1.mergeOptions(options.hls, options.vhs)) || this;
59686
59687      if (options.hls && Object.keys(options.hls).length) {
59688        videojs$1.log.warn('Using hls options is deprecated. Use vhs instead.');
59689      } // tech.player() is deprecated but setup a reference to HLS for
59690      // backwards-compatibility
59691
59692
59693      if (tech.options_ && tech.options_.playerId) {
59694        var _player = videojs$1(tech.options_.playerId);
59695
59696        if (!_player.hasOwnProperty('hls')) {
59697          Object.defineProperty(_player, 'hls', {
59698            get: function get() {
59699              videojs$1.log.warn('player.hls is deprecated. Use player.tech().vhs instead.');
59700              tech.trigger({
59701                type: 'usage',
59702                name: 'hls-player-access'
59703              });
59704              return assertThisInitialized(_this);
59705            },
59706            configurable: true
59707          });
59708        }
59709
59710        if (!_player.hasOwnProperty('vhs')) {
59711          Object.defineProperty(_player, 'vhs', {
59712            get: function get() {
59713              videojs$1.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.');
59714              tech.trigger({
59715                type: 'usage',
59716                name: 'vhs-player-access'
59717              });
59718              return assertThisInitialized(_this);
59719            },
59720            configurable: true
59721          });
59722        }
59723
59724        if (!_player.hasOwnProperty('dash')) {
59725          Object.defineProperty(_player, 'dash', {
59726            get: function get() {
59727              videojs$1.log.warn('player.dash is deprecated. Use player.tech().vhs instead.');
59728              return assertThisInitialized(_this);
59729            },
59730            configurable: true
59731          });
59732        }
59733
59734        _this.player_ = _player;
59735      }
59736
59737      _this.tech_ = tech;
59738      _this.source_ = source;
59739      _this.stats = {};
59740      _this.ignoreNextSeekingEvent_ = false;
59741
59742      _this.setOptions_();
59743
59744      if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
59745        tech.overrideNativeAudioTracks(true);
59746        tech.overrideNativeVideoTracks(true);
59747      } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
59748        // overriding native HLS only works if audio tracks have been emulated
59749        // error early if we're misconfigured
59750        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
59751      } // listen for fullscreenchange events for this player so that we
59752      // can adjust our quality selection quickly
59753
59754
59755      _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
59756        var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
59757
59758        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
59759          _this.masterPlaylistController_.smoothQualityChange_();
59760        }
59761      });
59762
59763      _this.on(_this.tech_, 'seeking', function () {
59764        if (this.ignoreNextSeekingEvent_) {
59765          this.ignoreNextSeekingEvent_ = false;
59766          return;
59767        }
59768
59769        this.setCurrentTime(this.tech_.currentTime());
59770      });
59771
59772      _this.on(_this.tech_, 'error', function () {
59773        // verify that the error was real and we are loaded
59774        // enough to have mpc loaded.
59775        if (this.tech_.error() && this.masterPlaylistController_) {
59776          this.masterPlaylistController_.pauseLoading();
59777        }
59778      });
59779
59780      _this.on(_this.tech_, 'play', _this.play);
59781
59782      return _this;
59783    }
59784
59785    var _proto = VhsHandler.prototype;
59786
59787    _proto.setOptions_ = function setOptions_() {
59788      var _this2 = this; // defaults
59789
59790
59791      this.options_.withCredentials = this.options_.withCredentials || false;
59792      this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true;
59793      this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
59794      this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
59795      this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
59796      this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
59797      this.options_.customTagParsers = this.options_.customTagParsers || [];
59798      this.options_.customTagMappers = this.options_.customTagMappers || [];
59799      this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
59800      this.options_.handlePartialData = this.options_.handlePartialData || false;
59801
59802      if (typeof this.options_.blacklistDuration !== 'number') {
59803        this.options_.blacklistDuration = 5 * 60;
59804      }
59805
59806      if (typeof this.options_.bandwidth !== 'number') {
59807        if (this.options_.useBandwidthFromLocalStorage) {
59808          var storedObject = getVhsLocalStorage();
59809
59810          if (storedObject && storedObject.bandwidth) {
59811            this.options_.bandwidth = storedObject.bandwidth;
59812            this.tech_.trigger({
59813              type: 'usage',
59814              name: 'vhs-bandwidth-from-local-storage'
59815            });
59816            this.tech_.trigger({
59817              type: 'usage',
59818              name: 'hls-bandwidth-from-local-storage'
59819            });
59820          }
59821
59822          if (storedObject && storedObject.throughput) {
59823            this.options_.throughput = storedObject.throughput;
59824            this.tech_.trigger({
59825              type: 'usage',
59826              name: 'vhs-throughput-from-local-storage'
59827            });
59828            this.tech_.trigger({
59829              type: 'usage',
59830              name: 'hls-throughput-from-local-storage'
59831            });
59832          }
59833        }
59834      } // if bandwidth was not set by options or pulled from local storage, start playlist
59835      // selection at a reasonable bandwidth
59836
59837
59838      if (typeof this.options_.bandwidth !== 'number') {
59839        this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
59840      } // If the bandwidth number is unchanged from the initial setting
59841      // then this takes precedence over the enableLowInitialPlaylist option
59842
59843
59844      this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
59845
59846      ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys', 'handlePartialData'].forEach(function (option) {
59847        if (typeof _this2.source_[option] !== 'undefined') {
59848          _this2.options_[option] = _this2.source_[option];
59849        }
59850      });
59851      this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
59852      this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
59853    }
59854    /**
59855     * called when player.src gets called, handle a new source
59856     *
59857     * @param {Object} src the source object to handle
59858     */
59859    ;
59860
59861    _proto.src = function src(_src, type) {
59862      var _this3 = this; // do nothing if the src is falsey
59863
59864
59865      if (!_src) {
59866        return;
59867      }
59868
59869      this.setOptions_(); // add master playlist controller options
59870
59871      this.options_.src = expandDataUri(this.source_.src);
59872      this.options_.tech = this.tech_;
59873      this.options_.externVhs = Vhs$1;
59874      this.options_.sourceType = mediaTypes_1(type); // Whenever we seek internally, we should update the tech
59875
59876      this.options_.seekTo = function (time) {
59877        _this3.tech_.setCurrentTime(time);
59878      };
59879
59880      this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
59881      this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
59882        seekable: function seekable() {
59883          return _this3.seekable();
59884        },
59885        media: function media() {
59886          return _this3.masterPlaylistController_.media();
59887        },
59888        masterPlaylistController: this.masterPlaylistController_
59889      }));
59890      this.masterPlaylistController_.on('error', function () {
59891        var player = videojs$1.players[_this3.tech_.options_.playerId];
59892        var error = _this3.masterPlaylistController_.error;
59893
59894        if (typeof error === 'object' && !error.code) {
59895          error.code = 3;
59896        } else if (typeof error === 'string') {
59897          error = {
59898            message: error,
59899            code: 3
59900          };
59901        }
59902
59903        player.error(error);
59904      }); // `this` in selectPlaylist should be the VhsHandler for backwards
59905      // compatibility with < v2
59906
59907      this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Vhs$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
59908      this.masterPlaylistController_.selectInitialPlaylist = Vhs$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
59909
59910      this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
59911      this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
59912      // controller. Using a custom property for backwards compatibility
59913      // with < v2
59914
59915      Object.defineProperties(this, {
59916        selectPlaylist: {
59917          get: function get() {
59918            return this.masterPlaylistController_.selectPlaylist;
59919          },
59920          set: function set(selectPlaylist) {
59921            this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
59922          }
59923        },
59924        throughput: {
59925          get: function get() {
59926            return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
59927          },
59928          set: function set(throughput) {
59929            this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
59930            // for the cumulative average
59931
59932            this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
59933          }
59934        },
59935        bandwidth: {
59936          get: function get() {
59937            return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
59938          },
59939          set: function set(bandwidth) {
59940            this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
59941            // `count` is set to zero that current value of `rate` isn't included
59942            // in the cumulative average
59943
59944            this.masterPlaylistController_.mainSegmentLoader_.throughput = {
59945              rate: 0,
59946              count: 0
59947            };
59948          }
59949        },
59950
59951        /**
59952         * `systemBandwidth` is a combination of two serial processes bit-rates. The first
59953         * is the network bitrate provided by `bandwidth` and the second is the bitrate of
59954         * the entire process after that - decryption, transmuxing, and appending - provided
59955         * by `throughput`.
59956         *
59957         * Since the two process are serial, the overall system bandwidth is given by:
59958         *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
59959         */
59960        systemBandwidth: {
59961          get: function get() {
59962            var invBandwidth = 1 / (this.bandwidth || 1);
59963            var invThroughput;
59964
59965            if (this.throughput > 0) {
59966              invThroughput = 1 / this.throughput;
59967            } else {
59968              invThroughput = 0;
59969            }
59970
59971            var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
59972            return systemBitrate;
59973          },
59974          set: function set() {
59975            videojs$1.log.error('The "systemBandwidth" property is read-only');
59976          }
59977        }
59978      });
59979
59980      if (this.options_.bandwidth) {
59981        this.bandwidth = this.options_.bandwidth;
59982      }
59983
59984      if (this.options_.throughput) {
59985        this.throughput = this.options_.throughput;
59986      }
59987
59988      Object.defineProperties(this.stats, {
59989        bandwidth: {
59990          get: function get() {
59991            return _this3.bandwidth || 0;
59992          },
59993          enumerable: true
59994        },
59995        mediaRequests: {
59996          get: function get() {
59997            return _this3.masterPlaylistController_.mediaRequests_() || 0;
59998          },
59999          enumerable: true
60000        },
60001        mediaRequestsAborted: {
60002          get: function get() {
60003            return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
60004          },
60005          enumerable: true
60006        },
60007        mediaRequestsTimedout: {
60008          get: function get() {
60009            return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
60010          },
60011          enumerable: true
60012        },
60013        mediaRequestsErrored: {
60014          get: function get() {
60015            return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
60016          },
60017          enumerable: true
60018        },
60019        mediaTransferDuration: {
60020          get: function get() {
60021            return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
60022          },
60023          enumerable: true
60024        },
60025        mediaBytesTransferred: {
60026          get: function get() {
60027            return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
60028          },
60029          enumerable: true
60030        },
60031        mediaSecondsLoaded: {
60032          get: function get() {
60033            return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
60034          },
60035          enumerable: true
60036        },
60037        buffered: {
60038          get: function get() {
60039            return timeRangesToArray(_this3.tech_.buffered());
60040          },
60041          enumerable: true
60042        },
60043        currentTime: {
60044          get: function get() {
60045            return _this3.tech_.currentTime();
60046          },
60047          enumerable: true
60048        },
60049        currentSource: {
60050          get: function get() {
60051            return _this3.tech_.currentSource_;
60052          },
60053          enumerable: true
60054        },
60055        currentTech: {
60056          get: function get() {
60057            return _this3.tech_.name_;
60058          },
60059          enumerable: true
60060        },
60061        duration: {
60062          get: function get() {
60063            return _this3.tech_.duration();
60064          },
60065          enumerable: true
60066        },
60067        master: {
60068          get: function get() {
60069            return _this3.playlists.master;
60070          },
60071          enumerable: true
60072        },
60073        playerDimensions: {
60074          get: function get() {
60075            return _this3.tech_.currentDimensions();
60076          },
60077          enumerable: true
60078        },
60079        seekable: {
60080          get: function get() {
60081            return timeRangesToArray(_this3.tech_.seekable());
60082          },
60083          enumerable: true
60084        },
60085        timestamp: {
60086          get: function get() {
60087            return Date.now();
60088          },
60089          enumerable: true
60090        },
60091        videoPlaybackQuality: {
60092          get: function get() {
60093            return _this3.tech_.getVideoPlaybackQuality();
60094          },
60095          enumerable: true
60096        }
60097      });
60098      this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
60099      this.tech_.on('bandwidthupdate', function () {
60100        if (_this3.options_.useBandwidthFromLocalStorage) {
60101          updateVhsLocalStorage({
60102            bandwidth: _this3.bandwidth,
60103            throughput: Math.round(_this3.throughput)
60104          });
60105        }
60106      });
60107      this.masterPlaylistController_.on('selectedinitialmedia', function () {
60108        // Add the manual rendition mix-in to VhsHandler
60109        renditionSelectionMixin(_this3);
60110      });
60111      this.masterPlaylistController_.sourceUpdater_.on('ready', function () {
60112        var audioPlaylistLoader = _this3.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
60113        setupEmeOptions({
60114          player: _this3.player_,
60115          sourceKeySystems: _this3.source_.keySystems,
60116          media: _this3.playlists.media(),
60117          audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
60118          mainPlaylists: _this3.playlists.master.playlists
60119        });
60120      }); // the bandwidth of the primary segment loader is our best
60121      // estimate of overall bandwidth
60122
60123      this.on(this.masterPlaylistController_, 'progress', function () {
60124        this.tech_.trigger('progress');
60125      }); // In the live case, we need to ignore the very first `seeking` event since
60126      // that will be the result of the seek-to-live behavior
60127
60128      this.on(this.masterPlaylistController_, 'firstplay', function () {
60129        this.ignoreNextSeekingEvent_ = true;
60130      });
60131      this.setupQualityLevels_(); // do nothing if the tech has been disposed already
60132      // this can occur if someone sets the src in player.ready(), for instance
60133
60134      if (!this.tech_.el()) {
60135        return;
60136      }
60137
60138      this.mediaSourceUrl_ = window$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource);
60139      this.tech_.src(this.mediaSourceUrl_);
60140    }
60141    /**
60142     * Initializes the quality levels and sets listeners to update them.
60143     *
60144     * @method setupQualityLevels_
60145     * @private
60146     */
60147    ;
60148
60149    _proto.setupQualityLevels_ = function setupQualityLevels_() {
60150      var _this4 = this;
60151
60152      var player = videojs$1.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
60153      // or qualityLevels_ listeners have already been setup, do nothing.
60154
60155      if (!player || !player.qualityLevels || this.qualityLevels_) {
60156        return;
60157      }
60158
60159      this.qualityLevels_ = player.qualityLevels();
60160      this.masterPlaylistController_.on('selectedinitialmedia', function () {
60161        handleVhsLoadedMetadata(_this4.qualityLevels_, _this4);
60162      });
60163      this.playlists.on('mediachange', function () {
60164        handleVhsMediaChange(_this4.qualityLevels_, _this4.playlists);
60165      });
60166    }
60167    /**
60168     * return the version
60169     */
60170    ;
60171
60172    VhsHandler.version = function version() {
60173      return {
60174        '@videojs/http-streaming': vhsVersion,
60175        'mux.js': muxVersion,
60176        'mpd-parser': mpdVersion,
60177        'm3u8-parser': m3u8Version,
60178        'aes-decrypter': aesVersion
60179      };
60180    }
60181    /**
60182     * return the version
60183     */
60184    ;
60185
60186    _proto.version = function version() {
60187      return this.constructor.version();
60188    };
60189
60190    _proto.canChangeType = function canChangeType() {
60191      return SourceUpdater.canChangeType();
60192    }
60193    /**
60194     * Begin playing the video.
60195     */
60196    ;
60197
60198    _proto.play = function play() {
60199      this.masterPlaylistController_.play();
60200    }
60201    /**
60202     * a wrapper around the function in MasterPlaylistController
60203     */
60204    ;
60205
60206    _proto.setCurrentTime = function setCurrentTime(currentTime) {
60207      this.masterPlaylistController_.setCurrentTime(currentTime);
60208    }
60209    /**
60210     * a wrapper around the function in MasterPlaylistController
60211     */
60212    ;
60213
60214    _proto.duration = function duration() {
60215      return this.masterPlaylistController_.duration();
60216    }
60217    /**
60218     * a wrapper around the function in MasterPlaylistController
60219     */
60220    ;
60221
60222    _proto.seekable = function seekable() {
60223      return this.masterPlaylistController_.seekable();
60224    }
60225    /**
60226     * Abort all outstanding work and cleanup.
60227     */
60228    ;
60229
60230    _proto.dispose = function dispose() {
60231      if (this.playbackWatcher_) {
60232        this.playbackWatcher_.dispose();
60233      }
60234
60235      if (this.masterPlaylistController_) {
60236        this.masterPlaylistController_.dispose();
60237      }
60238
60239      if (this.qualityLevels_) {
60240        this.qualityLevels_.dispose();
60241      }
60242
60243      if (this.player_) {
60244        delete this.player_.vhs;
60245        delete this.player_.dash;
60246        delete this.player_.hls;
60247      }
60248
60249      if (this.tech_ && this.tech_.vhs) {
60250        delete this.tech_.vhs;
60251      } // don't check this.tech_.hls as it will log a deprecated warning
60252
60253
60254      if (this.tech_) {
60255        delete this.tech_.hls;
60256      }
60257
60258      if (this.mediaSourceUrl_ && window$1.URL.revokeObjectURL) {
60259        window$1.URL.revokeObjectURL(this.mediaSourceUrl_);
60260        this.mediaSourceUrl_ = null;
60261      }
60262
60263      _Component.prototype.dispose.call(this);
60264    };
60265
60266    _proto.convertToProgramTime = function convertToProgramTime(time, callback) {
60267      return getProgramTime({
60268        playlist: this.masterPlaylistController_.media(),
60269        time: time,
60270        callback: callback
60271      });
60272    } // the player must be playing before calling this
60273    ;
60274
60275    _proto.seekToProgramTime = function seekToProgramTime$1(programTime, callback, pauseAfterSeek, retryCount) {
60276      if (pauseAfterSeek === void 0) {
60277        pauseAfterSeek = true;
60278      }
60279
60280      if (retryCount === void 0) {
60281        retryCount = 2;
60282      }
60283
60284      return seekToProgramTime({
60285        programTime: programTime,
60286        playlist: this.masterPlaylistController_.media(),
60287        retryCount: retryCount,
60288        pauseAfterSeek: pauseAfterSeek,
60289        seekTo: this.options_.seekTo,
60290        tech: this.options_.tech,
60291        callback: callback
60292      });
60293    };
60294
60295    return VhsHandler;
60296  }(Component$1);
60297  /**
60298   * The Source Handler object, which informs video.js what additional
60299   * MIME types are supported and sets up playback. It is registered
60300   * automatically to the appropriate tech based on the capabilities of
60301   * the browser it is running in. It is not necessary to use or modify
60302   * this object in normal usage.
60303   */
60304
60305
60306  var VhsSourceHandler = {
60307    name: 'videojs-http-streaming',
60308    VERSION: vhsVersion,
60309    canHandleSource: function canHandleSource(srcObj, options) {
60310      if (options === void 0) {
60311        options = {};
60312      }
60313
60314      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
60315      return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
60316    },
60317    handleSource: function handleSource(source, tech, options) {
60318      if (options === void 0) {
60319        options = {};
60320      }
60321
60322      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
60323      tech.vhs = new VhsHandler(source, tech, localOptions);
60324
60325      if (!videojs$1.hasOwnProperty('hls')) {
60326        Object.defineProperty(tech, 'hls', {
60327          get: function get() {
60328            videojs$1.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.');
60329            return tech.vhs;
60330          },
60331          configurable: true
60332        });
60333      }
60334
60335      tech.vhs.xhr = xhrFactory();
60336      tech.vhs.src(source.src, source.type);
60337      return tech.vhs;
60338    },
60339    canPlayType: function canPlayType(type, options) {
60340      if (options === void 0) {
60341        options = {};
60342      }
60343
60344      var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
60345          _videojs$mergeOptions2 = _videojs$mergeOptions.vhs.overrideNative,
60346          overrideNative = _videojs$mergeOptions2 === void 0 ? !videojs$1.browser.IS_ANY_SAFARI : _videojs$mergeOptions2;
60347
60348      var supportedType = mediaTypes_1(type);
60349      var canUseMsePlayback = supportedType && (!Vhs$1.supportsTypeNatively(supportedType) || overrideNative);
60350      return canUseMsePlayback ? 'maybe' : '';
60351    }
60352  };
60353  /**
60354   * Check to see if the native MediaSource object exists and supports
60355   * an MP4 container with both H.264 video and AAC-LC audio.
60356   *
60357   * @return {boolean} if  native media sources are supported
60358   */
60359
60360  var supportsNativeMediaSources = function supportsNativeMediaSources() {
60361    return codecs_3('avc1.4d400d,mp4a.40.2');
60362  }; // register source handlers with the appropriate techs
60363
60364
60365  if (supportsNativeMediaSources()) {
60366    videojs$1.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
60367  }
60368
60369  videojs$1.VhsHandler = VhsHandler;
60370  Object.defineProperty(videojs$1, 'HlsHandler', {
60371    get: function get() {
60372      videojs$1.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.');
60373      return VhsHandler;
60374    },
60375    configurable: true
60376  });
60377  videojs$1.VhsSourceHandler = VhsSourceHandler;
60378  Object.defineProperty(videojs$1, 'HlsSourceHandler', {
60379    get: function get() {
60380      videojs$1.log.warn('videojs.HlsSourceHandler is deprecated. ' + 'Use videojs.VhsSourceHandler instead.');
60381      return VhsSourceHandler;
60382    },
60383    configurable: true
60384  });
60385  videojs$1.Vhs = Vhs$1;
60386  Object.defineProperty(videojs$1, 'Hls', {
60387    get: function get() {
60388      videojs$1.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.');
60389      return Vhs$1;
60390    },
60391    configurable: true
60392  });
60393
60394  if (!videojs$1.use) {
60395    videojs$1.registerComponent('Hls', Vhs$1);
60396    videojs$1.registerComponent('Vhs', Vhs$1);
60397  }
60398
60399  videojs$1.options.vhs = videojs$1.options.vhs || {};
60400  videojs$1.options.hls = videojs$1.options.hls || {};
60401
60402  if (videojs$1.registerPlugin) {
60403    videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
60404  } else {
60405    videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
60406  }
60407
60408  return videojs$1;
60409
60410})));
60411
60412!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.10.2")}(window)}();

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.