PageSourceSearch

https://static.beyondlive.com/js/be/vjs.zencdn.net.video.js

js beyondlive.com collected 2026-09-24 17:49:25 UTC 2,162,862 bytes, 65,278 lines download raw bytes

1/**
2 * @license
3 * Video.js 7.15.4 <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/main/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/main/LICENSE>
11 */
12
13 window.vjsInit = function () {(function (global, factory) {
14  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15  typeof define === 'function' && define.amd ? define(factory) :
16  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
17}(this, (function () { 'use strict';
18
19  var version$5 = "7.15.4";
20
21  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
22
23  function createCommonjsModule(fn, module) {
24  	return module = { exports: {} }, fn(module, module.exports), module.exports;
25  }
26
27  var win;
28
29  if (typeof window !== "undefined") {
30    win = window;
31  } else if (typeof commonjsGlobal !== "undefined") {
32    win = commonjsGlobal;
33  } else if (typeof self !== "undefined") {
34    win = self;
35  } else {
36    win = {};
37  }
38
39  var window_1 = win;
40
41  /**
42   * An Object that contains lifecycle hooks as keys which point to an array
43   * of functions that are run when a lifecycle is triggered
44   *
45   * @private
46   */
47  var hooks_ = {};
48  /**
49   * Get a list of hooks for a specific lifecycle
50   *
51   * @param  {string} type
52   *         the lifecyle to get hooks from
53   *
54   * @param  {Function|Function[]} [fn]
55   *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
56   *
57   * @return {Array}
58   *         an array of hooks, or an empty array if there are none.
59   */
60
61  var hooks = function hooks(type, fn) {
62    hooks_[type] = hooks_[type] || [];
63
64    if (fn) {
65      hooks_[type] = hooks_[type].concat(fn);
66    }
67
68    return hooks_[type];
69  };
70  /**
71   * Add a function hook to a specific videojs lifecycle.
72   *
73   * @param {string} type
74   *        the lifecycle to hook the function to.
75   *
76   * @param {Function|Function[]}
77   *        The function or array of functions to attach.
78   */
79
80
81  var hook = function hook(type, fn) {
82    hooks(type, fn);
83  };
84  /**
85   * Remove a hook from a specific videojs lifecycle.
86   *
87   * @param  {string} type
88   *         the lifecycle that the function hooked to
89   *
90   * @param  {Function} fn
91   *         The hooked function to remove
92   *
93   * @return {boolean}
94   *         The function that was removed or undef
95   */
96
97
98  var removeHook = function removeHook(type, fn) {
99    var index = hooks(type).indexOf(fn);
100
101    if (index <= -1) {
102      return false;
103    }
104
105    hooks_[type] = hooks_[type].slice();
106    hooks_[type].splice(index, 1);
107    return true;
108  };
109  /**
110   * Add a function hook that will only run once to a specific videojs lifecycle.
111   *
112   * @param {string} type
113   *        the lifecycle to hook the function to.
114   *
115   * @param {Function|Function[]}
116   *        The function or array of functions to attach.
117   */
118
119
120  var hookOnce = function hookOnce(type, fn) {
121    hooks(type, [].concat(fn).map(function (original) {
122      var wrapper = function wrapper() {
123        removeHook(type, wrapper);
124        return original.apply(void 0, arguments);
125      };
126
127      return wrapper;
128    }));
129  };
130
131  var minDoc = {};
132
133  var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
134  var doccy;
135
136  if (typeof document !== 'undefined') {
137    doccy = document;
138  } else {
139    doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
140
141    if (!doccy) {
142      doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
143    }
144  }
145
146  var document_1 = doccy;
147
148  /**
149   * @file fullscreen-api.js
150   * @module fullscreen-api
151   * @private
152   */
153  /**
154   * Store the browser-specific methods for the fullscreen API.
155   *
156   * @type {Object}
157   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
158   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
159   */
160
161  var FullscreenApi = {
162    prefixed: true
163  }; // browser API methods
164
165  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
166  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
167  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
168  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
169  var specApi = apiMap[0];
170  var browserApi; // determine the supported set of functions
171
172  for (var i = 0; i < apiMap.length; i++) {
173    // check for exitFullscreen function
174    if (apiMap[i][1] in document_1) {
175      browserApi = apiMap[i];
176      break;
177    }
178  } // map the browser API names to the spec API names
179
180
181  if (browserApi) {
182    for (var _i = 0; _i < browserApi.length; _i++) {
183      FullscreenApi[specApi[_i]] = browserApi[_i];
184    }
185
186    FullscreenApi.prefixed = browserApi[0] !== specApi[0];
187  }
188
189  /**
190   * @file create-logger.js
191   * @module create-logger
192   */
193
194  var history = [];
195  /**
196   * Log messages to the console and history based on the type of message
197   *
198   * @private
199   * @param  {string} type
200   *         The name of the console method to use.
201   *
202   * @param  {Array} args
203   *         The arguments to be passed to the matching console method.
204   */
205
206  var LogByTypeFactory = function LogByTypeFactory(name, log) {
207    return function (type, level, args) {
208      var lvl = log.levels[level];
209      var lvlRegExp = new RegExp("^(" + lvl + ")$");
210
211      if (type !== 'log') {
212        // Add the type to the front of the message when it's not "log".
213        args.unshift(type.toUpperCase() + ':');
214      } // Add console prefix after adding to history.
215
216
217      args.unshift(name + ':'); // Add a clone of the args at this point to history.
218
219      if (history) {
220        history.push([].concat(args)); // only store 1000 history entries
221
222        var splice = history.length - 1000;
223        history.splice(0, splice > 0 ? splice : 0);
224      } // If there's no console then don't try to output messages, but they will
225      // still be stored in history.
226
227
228      if (!window_1.console) {
229        return;
230      } // Was setting these once outside of this function, but containing them
231      // in the function makes it easier to test cases where console doesn't exist
232      // when the module is executed.
233
234
235      var fn = window_1.console[type];
236
237      if (!fn && type === 'debug') {
238        // Certain browsers don't have support for console.debug. For those, we
239        // should default to the closest comparable log.
240        fn = window_1.console.info || window_1.console.log;
241      } // Bail out if there's no console or if this type is not allowed by the
242      // current logging level.
243
244
245      if (!fn || !lvl || !lvlRegExp.test(type)) {
246        return;
247      }
248
249      fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args);
250    };
251  };
252
253  function createLogger$1(name) {
254    // This is the private tracking variable for logging level.
255    var level = 'info'; // the curried logByType bound to the specific log and history
256
257    var logByType;
258    /**
259     * Logs plain debug messages. Similar to `console.log`.
260     *
261     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
262     * of our JSDoc template, we cannot properly document this as both a function
263     * and a namespace, so its function signature is documented here.
264     *
265     * #### Arguments
266     * ##### *args
267     * Mixed[]
268     *
269     * Any combination of values that could be passed to `console.log()`.
270     *
271     * #### Return Value
272     *
273     * `undefined`
274     *
275     * @namespace
276     * @param    {Mixed[]} args
277     *           One or more messages or objects that should be logged.
278     */
279
280    var log = function log() {
281      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
282        args[_key] = arguments[_key];
283      }
284
285      logByType('log', level, args);
286    }; // This is the logByType helper that the logging methods below use
287
288
289    logByType = LogByTypeFactory(name, log);
290    /**
291     * Create a new sublogger which chains the old name to the new name.
292     *
293     * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
294     * ```js
295     *  mylogger('foo');
296     *  // > VIDEOJS: player: foo
297     * ```
298     *
299     * @param {string} name
300     *        The name to add call the new logger
301     * @return {Object}
302     */
303
304    log.createLogger = function (subname) {
305      return createLogger$1(name + ': ' + subname);
306    };
307    /**
308     * Enumeration of available logging levels, where the keys are the level names
309     * and the values are `|`-separated strings containing logging methods allowed
310     * in that logging level. These strings are used to create a regular expression
311     * matching the function name being called.
312     *
313     * Levels provided by Video.js are:
314     *
315     * - `off`: Matches no calls. Any value that can be cast to `false` will have
316     *   this effect. The most restrictive.
317     * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
318     *   `log.warn`, and `log.error`).
319     * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
320     * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
321     * - `warn`: Matches `log.warn` and `log.error` calls.
322     * - `error`: Matches only `log.error` calls.
323     *
324     * @type {Object}
325     */
326
327
328    log.levels = {
329      all: 'debug|log|warn|error',
330      off: '',
331      debug: 'debug|log|warn|error',
332      info: 'log|warn|error',
333      warn: 'warn|error',
334      error: 'error',
335      DEFAULT: level
336    };
337    /**
338     * Get or set the current logging level.
339     *
340     * If a string matching a key from {@link module:log.levels} is provided, acts
341     * as a setter.
342     *
343     * @param  {string} [lvl]
344     *         Pass a valid level to set a new logging level.
345     *
346     * @return {string}
347     *         The current logging level.
348     */
349
350    log.level = function (lvl) {
351      if (typeof lvl === 'string') {
352        if (!log.levels.hasOwnProperty(lvl)) {
353          throw new Error("\"" + lvl + "\" in not a valid log level");
354        }
355
356        level = lvl;
357      }
358
359      return level;
360    };
361    /**
362     * Returns an array containing everything that has been logged to the history.
363     *
364     * This array is a shallow clone of the internal history record. However, its
365     * contents are _not_ cloned; so, mutating objects inside this array will
366     * mutate them in history.
367     *
368     * @return {Array}
369     */
370
371
372    log.history = function () {
373      return history ? [].concat(history) : [];
374    };
375    /**
376     * Allows you to filter the history by the given logger name
377     *
378     * @param {string} fname
379     *        The name to filter by
380     *
381     * @return {Array}
382     *         The filtered list to return
383     */
384
385
386    log.history.filter = function (fname) {
387      return (history || []).filter(function (historyItem) {
388        // if the first item in each historyItem includes `fname`, then it's a match
389        return new RegExp(".*" + fname + ".*").test(historyItem[0]);
390      });
391    };
392    /**
393     * Clears the internal history tracking, but does not prevent further history
394     * tracking.
395     */
396
397
398    log.history.clear = function () {
399      if (history) {
400        history.length = 0;
401      }
402    };
403    /**
404     * Disable history tracking if it is currently enabled.
405     */
406
407
408    log.history.disable = function () {
409      if (history !== null) {
410        history.length = 0;
411        history = null;
412      }
413    };
414    /**
415     * Enable history tracking if it is currently disabled.
416     */
417
418
419    log.history.enable = function () {
420      if (history === null) {
421        history = [];
422      }
423    };
424    /**
425     * Logs error messages. Similar to `console.error`.
426     *
427     * @param {Mixed[]} args
428     *        One or more messages or objects that should be logged as an error
429     */
430
431
432    log.error = function () {
433      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
434        args[_key2] = arguments[_key2];
435      }
436
437      return logByType('error', level, args);
438    };
439    /**
440     * Logs warning messages. Similar to `console.warn`.
441     *
442     * @param {Mixed[]} args
443     *        One or more messages or objects that should be logged as a warning.
444     */
445
446
447    log.warn = function () {
448      for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
449        args[_key3] = arguments[_key3];
450      }
451
452      return logByType('warn', level, args);
453    };
454    /**
455     * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
456     * log if `console.debug` is not available
457     *
458     * @param {Mixed[]} args
459     *        One or more messages or objects that should be logged as debug.
460     */
461
462
463    log.debug = function () {
464      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
465        args[_key4] = arguments[_key4];
466      }
467
468      return logByType('debug', level, args);
469    };
470
471    return log;
472  }
473
474  /**
475   * @file log.js
476   * @module log
477   */
478  var log$1 = createLogger$1('VIDEOJS');
479  var createLogger = log$1.createLogger;
480
481  var _extends_1 = createCommonjsModule(function (module) {
482    function _extends() {
483      module.exports = _extends = Object.assign || function (target) {
484        for (var i = 1; i < arguments.length; i++) {
485          var source = arguments[i];
486
487          for (var key in source) {
488            if (Object.prototype.hasOwnProperty.call(source, key)) {
489              target[key] = source[key];
490            }
491          }
492        }
493
494        return target;
495      };
496
497      return _extends.apply(this, arguments);
498    }
499
500    module.exports = _extends;
501  });
502
503  /**
504   * @file obj.js
505   * @module obj
506   */
507
508  /**
509   * @callback obj:EachCallback
510   *
511   * @param {Mixed} value
512   *        The current key for the object that is being iterated over.
513   *
514   * @param {string} key
515   *        The current key-value for object that is being iterated over
516   */
517
518  /**
519   * @callback obj:ReduceCallback
520   *
521   * @param {Mixed} accum
522   *        The value that is accumulating over the reduce loop.
523   *
524   * @param {Mixed} value
525   *        The current key for the object that is being iterated over.
526   *
527   * @param {string} key
528   *        The current key-value for object that is being iterated over
529   *
530   * @return {Mixed}
531   *         The new accumulated value.
532   */
533  var toString$1 = Object.prototype.toString;
534  /**
535   * Get the keys of an Object
536   *
537   * @param {Object}
538   *        The Object to get the keys from
539   *
540   * @return {string[]}
541   *         An array of the keys from the object. Returns an empty array if the
542   *         object passed in was invalid or had no keys.
543   *
544   * @private
545   */
546
547  var keys = function keys(object) {
548    return isObject$1(object) ? Object.keys(object) : [];
549  };
550  /**
551   * Array-like iteration for objects.
552   *
553   * @param {Object} object
554   *        The object to iterate over
555   *
556   * @param {obj:EachCallback} fn
557   *        The callback function which is called for each key in the object.
558   */
559
560
561  function each(object, fn) {
562    keys(object).forEach(function (key) {
563      return fn(object[key], key);
564    });
565  }
566  /**
567   * Array-like reduce for objects.
568   *
569   * @param {Object} object
570   *        The Object that you want to reduce.
571   *
572   * @param {Function} fn
573   *         A callback function which is called for each key in the object. It
574   *         receives the accumulated value and the per-iteration value and key
575   *         as arguments.
576   *
577   * @param {Mixed} [initial = 0]
578   *        Starting value
579   *
580   * @return {Mixed}
581   *         The final accumulated value.
582   */
583
584  function reduce(object, fn, initial) {
585    if (initial === void 0) {
586      initial = 0;
587    }
588
589    return keys(object).reduce(function (accum, key) {
590      return fn(accum, object[key], key);
591    }, initial);
592  }
593  /**
594   * Object.assign-style object shallow merge/extend.
595   *
596   * @param  {Object} target
597   * @param  {Object} ...sources
598   * @return {Object}
599   */
600
601  function assign(target) {
602    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
603      sources[_key - 1] = arguments[_key];
604    }
605
606    if (Object.assign) {
607      return _extends_1.apply(void 0, [target].concat(sources));
608    }
609
610    sources.forEach(function (source) {
611      if (!source) {
612        return;
613      }
614
615      each(source, function (value, key) {
616        target[key] = value;
617      });
618    });
619    return target;
620  }
621  /**
622   * Returns whether a value is an object of any kind - including DOM nodes,
623   * arrays, regular expressions, etc. Not functions, though.
624   *
625   * This avoids the gotcha where using `typeof` on a `null` value
626   * results in `'object'`.
627   *
628   * @param  {Object} value
629   * @return {boolean}
630   */
631
632  function isObject$1(value) {
633    return !!value && typeof value === 'object';
634  }
635  /**
636   * Returns whether an object appears to be a "plain" object - that is, a
637   * direct instance of `Object`.
638   *
639   * @param  {Object} value
640   * @return {boolean}
641   */
642
643  function isPlain(value) {
644    return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object;
645  }
646
647  /**
648   * @file computed-style.js
649   * @module computed-style
650   */
651  /**
652   * A safe getComputedStyle.
653   *
654   * This is needed because in Firefox, if the player is loaded in an iframe with
655   * `display:none`, then `getComputedStyle` returns `null`, so, we do a
656   * null-check to make sure that the player doesn't break in these cases.
657   *
658   * @function
659   * @param    {Element} el
660   *           The element you want the computed style of
661   *
662   * @param    {string} prop
663   *           The property name you want
664   *
665   * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
666   */
667
668  function computedStyle(el, prop) {
669    if (!el || !prop) {
670      return '';
671    }
672
673    if (typeof window_1.getComputedStyle === 'function') {
674      var computedStyleValue;
675
676      try {
677        computedStyleValue = window_1.getComputedStyle(el);
678      } catch (e) {
679        return '';
680      }
681
682      return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
683    }
684
685    return '';
686  }
687
688  /**
689   * @file browser.js
690   * @module browser
691   */
692  var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || '';
693  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
694  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
695  /**
696   * Whether or not this device is an iPod.
697   *
698   * @static
699   * @const
700   * @type {Boolean}
701   */
702
703  var IS_IPOD = /iPod/i.test(USER_AGENT);
704  /**
705   * The detected iOS version - or `null`.
706   *
707   * @static
708   * @const
709   * @type {string|null}
710   */
711
712  var IOS_VERSION = function () {
713    var match = USER_AGENT.match(/OS (\d+)_/i);
714
715    if (match && match[1]) {
716      return match[1];
717    }
718
719    return null;
720  }();
721  /**
722   * Whether or not this is an Android device.
723   *
724   * @static
725   * @const
726   * @type {Boolean}
727   */
728
729  var IS_ANDROID = /Android/i.test(USER_AGENT);
730  /**
731   * The detected Android version - or `null`.
732   *
733   * @static
734   * @const
735   * @type {number|string|null}
736   */
737
738  var ANDROID_VERSION = function () {
739    // This matches Android Major.Minor.Patch versions
740    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
741    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
742
743    if (!match) {
744      return null;
745    }
746
747    var major = match[1] && parseFloat(match[1]);
748    var minor = match[2] && parseFloat(match[2]);
749
750    if (major && minor) {
751      return parseFloat(match[1] + '.' + match[2]);
752    } else if (major) {
753      return major;
754    }
755
756    return null;
757  }();
758  /**
759   * Whether or not this is a native Android browser.
760   *
761   * @static
762   * @const
763   * @type {Boolean}
764   */
765
766  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
767  /**
768   * Whether or not this is Mozilla Firefox.
769   *
770   * @static
771   * @const
772   * @type {Boolean}
773   */
774
775  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
776  /**
777   * Whether or not this is Microsoft Edge.
778   *
779   * @static
780   * @const
781   * @type {Boolean}
782   */
783
784  var IS_EDGE = /Edg/i.test(USER_AGENT);
785  /**
786   * Whether or not this is Google Chrome.
787   *
788   * This will also be `true` for Chrome on iOS, which will have different support
789   * as it is actually Safari under the hood.
790   *
791   * @static
792   * @const
793   * @type {Boolean}
794   */
795
796  var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
797  /**
798   * The detected Google Chrome version - or `null`.
799   *
800   * @static
801   * @const
802   * @type {number|null}
803   */
804
805  var CHROME_VERSION = function () {
806    var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
807
808    if (match && match[2]) {
809      return parseFloat(match[2]);
810    }
811
812    return null;
813  }();
814  /**
815   * The detected Internet Explorer version - or `null`.
816   *
817   * @static
818   * @const
819   * @type {number|null}
820   */
821
822  var IE_VERSION = function () {
823    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
824    var version = result && parseFloat(result[1]);
825
826    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
827      // IE 11 has a different user agent string than other IE versions
828      version = 11.0;
829    }
830
831    return version;
832  }();
833  /**
834   * Whether or not this is desktop Safari.
835   *
836   * @static
837   * @const
838   * @type {Boolean}
839   */
840
841  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
842  /**
843   * Whether or not this is a Windows machine.
844   *
845   * @static
846   * @const
847   * @type {Boolean}
848   */
849
850  var IS_WINDOWS = /Windows/i.test(USER_AGENT);
851  /**
852   * Whether or not this device is touch-enabled.
853   *
854   * @static
855   * @const
856   * @type {Boolean}
857   */
858
859  var TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window_1 || window_1.navigator.maxTouchPoints || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch));
860  /**
861   * Whether or not this device is an iPad.
862   *
863   * @static
864   * @const
865   * @type {Boolean}
866   */
867
868  var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
869  /**
870   * Whether or not this device is an iPhone.
871   *
872   * @static
873   * @const
874   * @type {Boolean}
875   */
876  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
877  // to identify iPhones, we need to exclude iPads.
878  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
879
880  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
881  /**
882   * Whether or not this is an iOS device.
883   *
884   * @static
885   * @const
886   * @type {Boolean}
887   */
888
889  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
890  /**
891   * Whether or not this is any flavor of Safari - including iOS.
892   *
893   * @static
894   * @const
895   * @type {Boolean}
896   */
897
898  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
899
900  var browser = /*#__PURE__*/Object.freeze({
901    __proto__: null,
902    IS_IPOD: IS_IPOD,
903    IOS_VERSION: IOS_VERSION,
904    IS_ANDROID: IS_ANDROID,
905    ANDROID_VERSION: ANDROID_VERSION,
906    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
907    IS_FIREFOX: IS_FIREFOX,
908    IS_EDGE: IS_EDGE,
909    IS_CHROME: IS_CHROME,
910    CHROME_VERSION: CHROME_VERSION,
911    IE_VERSION: IE_VERSION,
912    IS_SAFARI: IS_SAFARI,
913    IS_WINDOWS: IS_WINDOWS,
914    TOUCH_ENABLED: TOUCH_ENABLED,
915    IS_IPAD: IS_IPAD,
916    IS_IPHONE: IS_IPHONE,
917    IS_IOS: IS_IOS,
918    IS_ANY_SAFARI: IS_ANY_SAFARI
919  });
920
921  /**
922   * @file dom.js
923   * @module dom
924   */
925  /**
926   * Detect if a value is a string with any non-whitespace characters.
927   *
928   * @private
929   * @param  {string} str
930   *         The string to check
931   *
932   * @return {boolean}
933   *         Will be `true` if the string is non-blank, `false` otherwise.
934   *
935   */
936
937  function isNonBlankString(str) {
938    // we use str.trim as it will trim any whitespace characters
939    // from the front or back of non-whitespace characters. aka
940    // Any string that contains non-whitespace characters will
941    // still contain them after `trim` but whitespace only strings
942    // will have a length of 0, failing this check.
943    return typeof str === 'string' && Boolean(str.trim());
944  }
945  /**
946   * Throws an error if the passed string has whitespace. This is used by
947   * class methods to be relatively consistent with the classList API.
948   *
949   * @private
950   * @param  {string} str
951   *         The string to check for whitespace.
952   *
953   * @throws {Error}
954   *         Throws an error if there is whitespace in the string.
955   */
956
957
958  function throwIfWhitespace(str) {
959    // str.indexOf instead of regex because str.indexOf is faster performance wise.
960    if (str.indexOf(' ') >= 0) {
961      throw new Error('class has illegal whitespace characters');
962    }
963  }
964  /**
965   * Produce a regular expression for matching a className within an elements className.
966   *
967   * @private
968   * @param  {string} className
969   *         The className to generate the RegExp for.
970   *
971   * @return {RegExp}
972   *         The RegExp that will check for a specific `className` in an elements
973   *         className.
974   */
975
976
977  function classRegExp(className) {
978    return new RegExp('(^|\\s)' + className + '($|\\s)');
979  }
980  /**
981   * Whether the current DOM interface appears to be real (i.e. not simulated).
982   *
983   * @return {boolean}
984   *         Will be `true` if the DOM appears to be real, `false` otherwise.
985   */
986
987
988  function isReal() {
989    // Both document and window will never be undefined thanks to `global`.
990    return document_1 === window_1.document;
991  }
992  /**
993   * Determines, via duck typing, whether or not a value is a DOM element.
994   *
995   * @param  {Mixed} value
996   *         The value to check.
997   *
998   * @return {boolean}
999   *         Will be `true` if the value is a DOM element, `false` otherwise.
1000   */
1001
1002  function isEl(value) {
1003    return isObject$1(value) && value.nodeType === 1;
1004  }
1005  /**
1006   * Determines if the current DOM is embedded in an iframe.
1007   *
1008   * @return {boolean}
1009   *         Will be `true` if the DOM is embedded in an iframe, `false`
1010   *         otherwise.
1011   */
1012
1013  function isInFrame() {
1014    // We need a try/catch here because Safari will throw errors when attempting
1015    // to get either `parent` or `self`
1016    try {
1017      return window_1.parent !== window_1.self;
1018    } catch (x) {
1019      return true;
1020    }
1021  }
1022  /**
1023   * Creates functions to query the DOM using a given method.
1024   *
1025   * @private
1026   * @param   {string} method
1027   *          The method to create the query with.
1028   *
1029   * @return  {Function}
1030   *          The query method
1031   */
1032
1033  function createQuerier(method) {
1034    return function (selector, context) {
1035      if (!isNonBlankString(selector)) {
1036        return document_1[method](null);
1037      }
1038
1039      if (isNonBlankString(context)) {
1040        context = document_1.querySelector(context);
1041      }
1042
1043      var ctx = isEl(context) ? context : document_1;
1044      return ctx[method] && ctx[method](selector);
1045    };
1046  }
1047  /**
1048   * Creates an element and applies properties, attributes, and inserts content.
1049   *
1050   * @param  {string} [tagName='div']
1051   *         Name of tag to be created.
1052   *
1053   * @param  {Object} [properties={}]
1054   *         Element properties to be applied.
1055   *
1056   * @param  {Object} [attributes={}]
1057   *         Element attributes to be applied.
1058   *
1059   * @param {module:dom~ContentDescriptor} content
1060   *        A content descriptor object.
1061   *
1062   * @return {Element}
1063   *         The element that was created.
1064   */
1065
1066
1067  function createEl(tagName, properties, attributes, content) {
1068    if (tagName === void 0) {
1069      tagName = 'div';
1070    }
1071
1072    if (properties === void 0) {
1073      properties = {};
1074    }
1075
1076    if (attributes === void 0) {
1077      attributes = {};
1078    }
1079
1080    var el = document_1.createElement(tagName);
1081    Object.getOwnPropertyNames(properties).forEach(function (propName) {
1082      var val = properties[propName]; // See #2176
1083      // We originally were accepting both properties and attributes in the
1084      // same object, but that doesn't work so well.
1085
1086      if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
1087        log$1.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 + "."));
1088        el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
1089        // method for it.
1090      } else if (propName === 'textContent') {
1091        textContent(el, val);
1092      } else if (el[propName] !== val || propName === 'tabIndex') {
1093        el[propName] = val;
1094      }
1095    });
vendor: 9,134 bytes, lines 1096-1415
1096    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1097      el.setAttribute(attrName, attributes[attrName]);
1098    });
1099
1100    if (content) {
1101      appendContent(el, content);
1102    }
1103
1104    return el;
1105  }
1106  /**
1107   * Injects text into an element, replacing any existing contents entirely.
1108   *
1109   * @param  {Element} el
1110   *         The element to add text content into
1111   *
1112   * @param  {string} text
1113   *         The text content to add.
1114   *
1115   * @return {Element}
1116   *         The element with added text content.
1117   */
1118
1119  function textContent(el, text) {
1120    if (typeof el.textContent === 'undefined') {
1121      el.innerText = text;
1122    } else {
1123      el.textContent = text;
1124    }
1125
1126    return el;
1127  }
1128  /**
1129   * Insert an element as the first child node of another
1130   *
1131   * @param {Element} child
1132   *        Element to insert
1133   *
1134   * @param {Element} parent
1135   *        Element to insert child into
1136   */
1137
1138  function prependTo(child, parent) {
1139    if (parent.firstChild) {
1140      parent.insertBefore(child, parent.firstChild);
1141    } else {
1142      parent.appendChild(child);
1143    }
1144  }
1145  /**
1146   * Check if an element has a class name.
1147   *
1148   * @param  {Element} element
1149   *         Element to check
1150   *
1151   * @param  {string} classToCheck
1152   *         Class name to check for
1153   *
1154   * @return {boolean}
1155   *         Will be `true` if the element has a class, `false` otherwise.
1156   *
1157   * @throws {Error}
1158   *         Throws an error if `classToCheck` has white space.
1159   */
1160
1161  function hasClass(element, classToCheck) {
1162    throwIfWhitespace(classToCheck);
1163
1164    if (element.classList) {
1165      return element.classList.contains(classToCheck);
1166    }
1167
1168    return classRegExp(classToCheck).test(element.className);
1169  }
1170  /**
1171   * Add a class name to an element.
1172   *
1173   * @param  {Element} element
1174   *         Element to add class name to.
1175   *
1176   * @param  {string} classToAdd
1177   *         Class name to add.
1178   *
1179   * @return {Element}
1180   *         The DOM element with the added class name.
1181   */
1182
1183  function addClass(element, classToAdd) {
1184    if (element.classList) {
1185      element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
1186      // in the case of classList not being supported.
1187    } else if (!hasClass(element, classToAdd)) {
1188      element.className = (element.className + ' ' + classToAdd).trim();
1189    }
1190
1191    return element;
1192  }
1193  /**
1194   * Remove a class name from an element.
1195   *
1196   * @param  {Element} element
1197   *         Element to remove a class name from.
1198   *
1199   * @param  {string} classToRemove
1200   *         Class name to remove
1201   *
1202   * @return {Element}
1203   *         The DOM element with class name removed.
1204   */
1205
1206  function removeClass(element, classToRemove) {
1207    // Protect in case the player gets disposed
1208    if (!element) {
1209      log$1.warn("removeClass was called with an element that doesn't exist");
1210      return null;
1211    }
1212
1213    if (element.classList) {
1214      element.classList.remove(classToRemove);
1215    } else {
1216      throwIfWhitespace(classToRemove);
1217      element.className = element.className.split(/\s+/).filter(function (c) {
1218        return c !== classToRemove;
1219      }).join(' ');
1220    }
1221
1222    return element;
1223  }
1224  /**
1225   * The callback definition for toggleClass.
1226   *
1227   * @callback module:dom~PredicateCallback
1228   * @param    {Element} element
1229   *           The DOM element of the Component.
1230   *
1231   * @param    {string} classToToggle
1232   *           The `className` that wants to be toggled
1233   *
1234   * @return   {boolean|undefined}
1235   *           If `true` is returned, the `classToToggle` will be added to the
1236   *           `element`. If `false`, the `classToToggle` will be removed from
1237   *           the `element`. If `undefined`, the callback will be ignored.
1238   */
1239
1240  /**
1241   * Adds or removes a class name to/from an element depending on an optional
1242   * condition or the presence/absence of the class name.
1243   *
1244   * @param  {Element} element
1245   *         The element to toggle a class name on.
1246   *
1247   * @param  {string} classToToggle
1248   *         The class that should be toggled.
1249   *
1250   * @param  {boolean|module:dom~PredicateCallback} [predicate]
1251   *         See the return value for {@link module:dom~PredicateCallback}
1252   *
1253   * @return {Element}
1254   *         The element with a class that has been toggled.
1255   */
1256
1257  function toggleClass(element, classToToggle, predicate) {
1258    // This CANNOT use `classList` internally because IE11 does not support the
1259    // second parameter to the `classList.toggle()` method! Which is fine because
1260    // `classList` will be used by the add/remove functions.
1261    var has = hasClass(element, classToToggle);
1262
1263    if (typeof predicate === 'function') {
1264      predicate = predicate(element, classToToggle);
1265    }
1266
1267    if (typeof predicate !== 'boolean') {
1268      predicate = !has;
1269    } // If the necessary class operation matches the current state of the
1270    // element, no action is required.
1271
1272
1273    if (predicate === has) {
1274      return;
1275    }
1276
1277    if (predicate) {
1278      addClass(element, classToToggle);
1279    } else {
1280      removeClass(element, classToToggle);
1281    }
1282
1283    return element;
1284  }
1285  /**
1286   * Apply attributes to an HTML element.
1287   *
1288   * @param {Element} el
1289   *        Element to add attributes to.
1290   *
1291   * @param {Object} [attributes]
1292   *        Attributes to be applied.
1293   */
1294
1295  function setAttributes(el, attributes) {
1296    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1297      var attrValue = attributes[attrName];
1298
1299      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1300        el.removeAttribute(attrName);
1301      } else {
1302        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
1303      }
1304    });
1305  }
1306  /**
1307   * Get an element's attribute values, as defined on the HTML tag.
1308   *
1309   * Attributes are not the same as properties. They're defined on the tag
1310   * or with setAttribute.
1311   *
1312   * @param  {Element} tag
1313   *         Element from which to get tag attributes.
1314   *
1315   * @return {Object}
1316   *         All attributes of the element. Boolean attributes will be `true` or
1317   *         `false`, others will be strings.
1318   */
1319
1320  function getAttributes(tag) {
1321    var obj = {}; // known boolean attributes
1322    // we can check for matching boolean properties, but not all browsers
1323    // and not all tags know about these attributes, so, we still want to check them manually
1324
1325    var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
1326
1327    if (tag && tag.attributes && tag.attributes.length > 0) {
1328      var attrs = tag.attributes;
1329
1330      for (var i = attrs.length - 1; i >= 0; i--) {
1331        var attrName = attrs[i].name;
1332        var attrVal = attrs[i].value; // check for known booleans
1333        // the matching element property will return a value for typeof
1334
1335        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
1336          // the value of an included boolean attribute is typically an empty
1337          // string ('') which would equal false if we just check for a false value.
1338          // we also don't want support bad code like autoplay='false'
1339          attrVal = attrVal !== null ? true : false;
1340        }
1341
1342        obj[attrName] = attrVal;
1343      }
1344    }
1345
1346    return obj;
1347  }
1348  /**
1349   * Get the value of an element's attribute.
1350   *
1351   * @param {Element} el
1352   *        A DOM element.
1353   *
1354   * @param {string} attribute
1355   *        Attribute to get the value of.
1356   *
1357   * @return {string}
1358   *         The value of the attribute.
1359   */
1360
1361  function getAttribute(el, attribute) {
1362    return el.getAttribute(attribute);
1363  }
1364  /**
1365   * Set the value of an element's attribute.
1366   *
1367   * @param {Element} el
1368   *        A DOM element.
1369   *
1370   * @param {string} attribute
1371   *        Attribute to set.
1372   *
1373   * @param {string} value
1374   *        Value to set the attribute to.
1375   */
1376
1377  function setAttribute(el, attribute, value) {
1378    el.setAttribute(attribute, value);
1379  }
1380  /**
1381   * Remove an element's attribute.
1382   *
1383   * @param {Element} el
1384   *        A DOM element.
1385   *
1386   * @param {string} attribute
1387   *        Attribute to remove.
1388   */
1389
1390  function removeAttribute(el, attribute) {
1391    el.removeAttribute(attribute);
1392  }
1393  /**
1394   * Attempt to block the ability to select text.
1395   */
1396
1397  function blockTextSelection() {
1398    document_1.body.focus();
1399
1400    document_1.onselectstart = function () {
1401      return false;
1402    };
1403  }
1404  /**
1405   * Turn off text selection blocking.
1406   */
1407
1408  function unblockTextSelection() {
1409    document_1.onselectstart = function () {
1410      return true;
1411    };
1412  }
1413  /**
1414   * Identical to the native `getBoundingClientRect` function, but ensures that
1415   * the method is supported at all (it is in all browsers we claim to supp
1415ort)
1416   * and that the element is in the DOM before continuing.
1417   *
1418   * This wrapper function also shims properties which are not provided by some
1419   * older browsers (namely, IE8).
1420   *
1421   * Additionally, some browsers do not support adding properties to a
1422   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1423   * properties (except `x` and `y` which are not widely supported). This helps
1424   * avoid implementations where keys are non-enumerable.
1425   *
1426   * @param  {Element} el
1427   *         Element whose `ClientRect` we want to calculate.
1428   *
1429   * @return {Object|undefined}
1430   *         Always returns a plain object - or `undefined` if it cannot.
1431   */
1432
1433  function getBoundingClientRect(el) {
1434    if (el && el.getBoundingClientRect && el.parentNode) {
1435      var rect = el.getBoundingClientRect();
1436      var result = {};
1437      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1438        if (rect[k] !== undefined) {
1439          result[k] = rect[k];
1440        }
1441      });
1442
1443      if (!result.height) {
1444        result.height = parseFloat(computedStyle(el, 'height'));
1445      }
1446
1447      if (!result.width) {
1448        result.width = parseFloat(computedStyle(el, 'width'));
1449      }
1450
1451      return result;
1452    }
1453  }
1454  /**
1455   * Represents the position of a DOM element on the page.
1456   *
1457   * @typedef  {Object} module:dom~Position
1458   *
1459   * @property {number} left
1460   *           Pixels to the left.
1461   *
1462   * @property {number} top
1463   *           Pixels from the top.
1464   */
1465
1466  /**
1467   * Get the position of an element in the DOM.
1468   *
1469   * Uses `getBoundingClientRect` technique from John Resig.
1470   *
1471   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1472   *
1473   * @param  {Element} el
1474   *         Element from which to get offset.
1475   *
1476   * @return {module:dom~Position}
1477   *         The position of the element that was passed in.
1478   */
1479
1480  function findPosition(el) {
1481    if (!el || el && !el.offsetParent) {
1482      return {
1483        left: 0,
1484        top: 0,
1485        width: 0,
1486        height: 0
1487      };
1488    }
1489
1490    var width = el.offsetWidth;
1491    var height = el.offsetHeight;
1492    var left = 0;
1493    var top = 0;
1494
1495    while (el.offsetParent && el !== document_1[FullscreenApi.fullscreenElement]) {
1496      left += el.offsetLeft;
1497      top += el.offsetTop;
1498      el = el.offsetParent;
1499    }
1500
1501    return {
1502      left: left,
1503      top: top,
1504      width: width,
1505      height: height
1506    };
1507  }
1508  /**
1509   * Represents x and y coordinates for a DOM element or mouse pointer.
1510   *
1511   * @typedef  {Object} module:dom~Coordinates
1512   *
1513   * @property {number} x
1514   *           x coordinate in pixels
1515   *
1516   * @property {number} y
1517   *           y coordinate in pixels
1518   */
1519
1520  /**
1521   * Get the pointer position within an element.
1522   *
1523   * The base on the coordinates are the bottom left of the element.
1524   *
1525   * @param  {Element} el
1526   *         Element on which to get the pointer position on.
1527   *
1528   * @param  {EventTarget~Event} event
1529   *         Event object.
1530   *
1531   * @return {module:dom~Coordinates}
1532   *         A coordinates object corresponding to the mouse position.
1533   *
1534   */
1535
1536  function getPointerPosition(el, event) {
1537    var translated = {
1538      x: 0,
1539      y: 0
1540    };
1541
1542    if (IS_IOS) {
1543      var item = el;
1544
1545      while (item && item.nodeName.toLowerCase() !== 'html') {
1546        var transform = computedStyle(item, 'transform');
1547
1548        if (/^matrix/.test(transform)) {
1549          var values = transform.slice(7, -1).split(/,\s/).map(Number);
1550          translated.x += values[4];
1551          translated.y += values[5];
1552        } else if (/^matrix3d/.test(transform)) {
1553          var _values = transform.slice(9, -1).split(/,\s/).map(Number);
1554
1555          translated.x += _values[12];
1556          translated.y += _values[13];
1557        }
1558
1559        item = item.parentNode;
1560      }
1561    }
1562
1563    var position = {};
1564    var boxTarget = findPosition(event.target);
1565    var box = findPosition(el);
1566    var boxW = box.width;
1567    var boxH = box.height;
1568    var offsetY = event.offsetY - (box.top - boxTarget.top);
1569    var offsetX = event.offsetX - (box.left - boxTarget.left);
1570
1571    if (event.changedTouches) {
1572      offsetX = event.changedTouches[0].pageX - box.left;
1573      offsetY = event.changedTouches[0].pageY + box.top;
1574
1575      if (IS_IOS) {
1576        offsetX -= translated.x;
1577        offsetY -= translated.y;
1578      }
1579    }
1580
1581    position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
1582    position.x = Math.max(0, Math.min(1, offsetX / boxW));
1583    return position;
1584  }
1585  /**
1586   * Determines, via duck typing, whether or not a value is a text node.
1587   *
1588   * @param  {Mixed} value
1589   *         Check if this value is a text node.
1590   *
1591   * @return {boolean}
1592   *         Will be `true` if the value is a text node, `false` otherwise.
1593   */
1594
1595  function isTextNode(value) {
1596    return isObject$1(value) && value.nodeType === 3;
1597  }
1598  /**
1599   * Empties the contents of an element.
1600   *
1601   * @param  {Element} el
1602   *         The element to empty children from
1603   *
1604   * @return {Element}
1605   *         The element with no children
1606   */
1607
1608  function emptyEl(el) {
1609    while (el.firstChild) {
1610      el.removeChild(el.firstChild);
1611    }
1612
1613    return el;
1614  }
1615  /**
1616   * This is a mixed value that describes content to be injected into the DOM
1617   * via some method. It can be of the following types:
1618   *
1619   * Type       | Description
1620   * -----------|-------------
1621   * `string`   | The value will be normalized into a text node.
1622   * `Element`  | The value will be accepted as-is.
1623   * `TextNode` | The value will be accepted as-is.
1624   * `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).
1625   * `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.
1626   *
1627   * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
1628   */
1629
1630  /**
1631   * Normalizes content for eventual insertion into the DOM.
1632   *
1633   * This allows a wide range of content definition methods, but helps protect
1634   * from falling into the trap of simply writing to `innerHTML`, which could
1635   * be an XSS concern.
1636   *
1637   * The content for an element can be passed in multiple types and
1638   * combinations, whose behavior is as follows:
1639   *
1640   * @param {module:dom~ContentDescriptor} content
1641   *        A content descriptor value.
1642   *
1643   * @return {Array}
1644   *         All of the content that was passed in, normalized to an array of
1645   *         elements or text nodes.
1646   */
1647
1648  function normalizeContent(content) {
1649    // First, invoke content if it is a function. If it produces an array,
1650    // that needs to happen before normalization.
1651    if (typeof content === 'function') {
1652      content = content();
1653    } // Next up, normalize to an array, so one or many items can be normalized,
1654    // filtered, and returned.
1655
1656
1657    return (Array.isArray(content) ? content : [content]).map(function (value) {
1658      // First, invoke value if it is a function to produce a new value,
1659      // which will be subsequently normalized to a Node of some kind.
1660      if (typeof value === 'function') {
1661        value = value();
1662      }
1663
1664      if (isEl(value) || isTextNode(value)) {
1665        return value;
1666      }
1667
1668      if (typeof value === 'string' && /\S/.test(value)) {
1669        return document_1.createTextNode(value);
1670      }
1671    }).filter(function (value) {
1672      return value;
1673    });
1674  }
1675  /**
1676   * Normalizes and appends content to an element.
1677   *
1678   * @param  {Element} el
1679   *         Element to append normalized content to.
1680   *
1681   * @param {module:dom~ContentDescriptor} content
1682   *        A content descriptor value.
1683   *
1684   * @return {Element}
1685   *         The element with appended normalized content.
1686   */
1687
1688  function appendContent(el, content) {
1689    normalizeContent(content).forEach(function (node) {
1690      return el.appendChild(node);
1691    });
1692    return el;
1693  }
1694  /**
1695   * Normalizes and inserts content into an element; this is identical to
1696   * `appendContent()`, except it empties the element first.
1697   *
1698   * @param {Element} el
1699   *        Element to insert normalized content into.
1700   *
1701   * @param {module:dom~ContentDescriptor} content
1702   *        A content descriptor value.
1703   *
1704   * @return {Element}
1705   *         The element with inserted normalized content.
1706   */
1707
1708  function insertContent(el, content) {
1709    return appendContent(emptyEl(el), content);
1710  }
1711  /**
1712   * Check if an event was a single left click.
1713   *
1714   * @param  {EventTarget~Event} event
1715   *         Event object.
1716   *
1717   * @return {boolean}
1718   *         Will be `true` if a single left click, `false` otherwise.
1719   */
1720
1721  function isSingleLeftClick(event) {
1722    // Note: if you create something draggable, be sure to
1723    // call it on both `mousedown` and `mousemove` event,
1724    // otherwise `mousedown` should be enough for a button
1725    if (event.button === undefined && event.buttons === undefined) {
1726      // Why do we need `buttons` ?
1727      // Because, middle mouse sometimes have this:
1728      // e.button === 0 and e.buttons === 4
1729      // Furthermore, we want to prevent combination click, something like
1730      // HOLD middlemouse then left click, that would be
1731      // e.button === 0, e.buttons === 5
1732      // just `button` is not gonna work
1733      // Alright, then what this block does ?
1734      // this is for chrome `simulate mobile devices`
1735      // I want to support this as well
1736      return true;
1737    }
1738
1739    if (event.button === 0 && event.buttons === undefined) {
1740      // Touch screen, sometimes on some specific device, `buttons`
1741      // doesn't have anything (safari on ios, blackberry...)
1742      return true;
1743    } // `mouseup` event on a single left click has
1744    // `button` and `buttons` equal to 0
1745
1746
1747    if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
1748      return true;
1749    }
1750
1751    if (event.button !== 0 || event.buttons !== 1) {
1752      // This is the reason we have those if else block above
1753      // if any special case we can catch and let it slide
1754      // we do it above, when get to here, this definitely
1755      // is-not-left-click
1756      return false;
1757    }
1758
1759    return true;
1760  }
1761  /**
1762   * Finds a single DOM element matching `selector` within the optional
1763   * `context` of another DOM element (defaulting to `document`).
1764   *
1765   * @param  {string} selector
1766   *         A valid CSS selector, which will be passed to `querySelector`.
1767   *
1768   * @param  {Element|String} [context=document]
1769   *         A DOM element within which to query. Can also be a selector
1770   *         string in which case the first matching element will be used
1771   *         as context. If missing (or no element matches selector), falls
1772   *         back to `document`.
1773   *
1774   * @return {Element|null}
1775   *         The element that was found or null.
1776   */
1777
1778  var $ = createQuerier('querySelector');
1779  /**
1780   * Finds a all DOM elements matching `selector` within the optional
1781   * `context` of another DOM element (defaulting to `document`).
1782   *
1783   * @param  {string} selector
1784   *         A valid CSS selector, which will be passed to `querySelectorAll`.
1785   *
1786   * @param  {Element|String} [context=document]
1787   *         A DOM element within which to query. Can also be a selector
1788   *         string in which case the first matching element will be used
1789   *         as context. If missing (or no element matches selector), falls
1790   *         back to `document`.
1791   *
1792   * @return {NodeList}
1793   *         A element list of elements that were found. Will be empty if none
1794   *         were found.
1795   *
1796   */
1797
1798  var $$ = createQuerier('querySelectorAll');
1799
1800  var Dom = /*#__PURE__*/Object.freeze({
1801    __proto__: null,
1802    isReal: isReal,
1803    isEl: isEl,
1804    isInFrame: isInFrame,
1805    createEl: createEl,
1806    textContent: textContent,
1807    prependTo: prependTo,
1808    hasClass: hasClass,
1809    addClass: addClass,
1810    removeClass: removeClass,
1811    toggleClass: toggleClass,
1812    setAttributes: setAttributes,
1813    getAttributes: getAttributes,
1814    getAttribute: getAttribute,
1815    setAttribute: setAttribute,
1816    removeAttribute: removeAttribute,
1817    blockTextSelection: blockTextSelection,
1818    unblockTextSelection: unblockTextSelection,
1819    getBoundingClientRect: getBoundingClientRect,
1820    findPosition: findPosition,
1821    getPointerPosition: getPointerPosition,
1822    isTextNode: isTextNode,
1823    emptyEl: emptyEl,
1824    normalizeContent: normalizeContent,
1825    appendContent: appendContent,
1826    insertContent: insertContent,
1827    isSingleLeftClick: isSingleLeftClick,
1828    $: $,
1829    $$: $$
1830  });
1831
1832  /**
1833   * @file setup.js - Functions for setting up a player without
1834   * user interaction based on the data-setup `attribute` of the video tag.
1835   *
1836   * @module setup
1837   */
1838  var _windowLoaded = false;
1839  var videojs$1;
1840  /**
1841   * Set up any tags that have a data-setup `attribute` when the player is started.
1842   */
1843
1844  var autoSetup = function autoSetup() {
1845    if (videojs$1.options.autoSetup === false) {
1846      return;
1847    }
1848
1849    var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video'));
1850    var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio'));
1851    var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js'));
1852    var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
1853
1854    if (mediaEls && mediaEls.length > 0) {
1855      for (var i = 0, e = mediaEls.length; i < e; i++) {
1856        var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
1857
1858        if (mediaEl && mediaEl.getAttribute) {
1859          // Make sure this player hasn't already been set up.
1860          if (mediaEl.player === undefined) {
1861            var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
1862            // We only auto-setup if they've added the data-setup attr.
1863
1864            if (options !== null) {
1865              // Create new video.js instance.
1866              videojs$1(mediaEl);
1867            }
1868          } // If getAttribute isn't defined, we need to wait for the DOM.
1869
1870        } else {
1871          autoSetupTimeout(1);
1872          break;
1873        }
1874      } // No videos were found, so keep looping unless page is finished loading.
1875
1876    } else if (!_windowLoaded) {
1877      autoSetupTimeout(1);
1878    }
1879  };
1880  /**
1881   * Wait until the page is loaded before running autoSetup. This will be called in
1882   * autoSetup if `hasLoaded` returns false.
1883   *
1884   * @param {number} wait
1885   *        How long to wait in ms
1886   *
1887   * @param {module:videojs} [vjs]
1888   *        The videojs library function
1889   */
1890
1891
1892  function autoSetupTimeout(wait, vjs) {
1893    // Protect against breakage in non-browser environments
1894    if (!isReal()) {
1895      return;
1896    }
1897
1898    if (vjs) {
1899      videojs$1 = vjs;
1900    }
1901
1902    window_1.setTimeout(autoSetup, wait);
1903  }
1904  /**
1905   * Used to set the internal tracking of window loaded state to true.
1906   *
1907   * @private
1908   */
1909
1910
1911  function setWindowLoaded() {
1912    _windowLoaded = true;
1913    window_1.removeEventListener('load', setWindowLoaded);
1914  }
1915
1916  if (isReal()) {
1917    if (document_1.readyState === 'complete') {
1918      setWindowLoaded();
1919    } else {
1920      /**
1921       * Listen for the load event on window, and set _windowLoaded to true.
1922       *
1923       * We use a standard event listener here to avoid incrementing the GUID
1924       * before any players are created.
1925       *
1926       * @listens load
1927       */
1928      window_1.addEventListener('load', setWindowLoaded);
1929    }
1930  }
1931
1932  /**
1933   * @file stylesheet.js
1934   * @module stylesheet
1935   */
1936  /**
1937   * Create a DOM syle element given a className for it.
1938   *
1939   * @param {string} className
1940   *        The className to add to the created style element.
1941   *
1942   * @return {Element}
1943   *         The element that was created.
1944   */
1945
1946  var createStyleElement = function createStyleElement(className) {
1947    var style = document_1.createElement('style');
1948    style.className = className;
1949    return style;
1950  };
1951  /**
1952   * Add text to a DOM element.
1953   *
1954   * @param {Element} el
1955   *        The Element to add text content to.
1956   *
1957   * @param {string} content
1958   *        The text to add to the element.
1959   */
1960
1961  var setTextContent = function setTextContent(el, content) {
1962    if (el.styleSheet) {
1963      el.styleSheet.cssText = content;
1964    } else {
1965      el.textContent = content;
1966    }
1967  };
1968
1969  /**
1970   * @file guid.js
1971   * @module guid
1972   */
1973  // Default value for GUIDs. This allows us to reset the GUID counter in tests.
1974  //
1975  // The initial GUID is 3 because some users have come to rely on the first
1976  // default player ID ending up as `vjs_video_3`.
1977  //
1978  // See: https://github.com/videojs/video.js/pull/6216
1979  var _initialGuid = 3;
1980  /**
1981   * Unique ID for an element or function
1982   *
1983   * @type {Number}
1984   */
1985
1986  var _guid = _initialGuid;
1987  /**
1988   * Get a unique auto-incrementing ID by number that has not been returned before.
1989   *
1990   * @return {number}
1991   *         A new unique ID.
1992   */
1993
1994  function newGUID() {
1995    return _guid++;
1996  }
1997
1998  /**
1999   * @file dom-data.js
2000   * @module dom-data
2001   */
2002  var FakeWeakMap;
2003
2004  if (!window_1.WeakMap) {
2005    FakeWeakMap = /*#__PURE__*/function () {
2006      function FakeWeakMap() {
2007        this.vdata = 'vdata' + Math.floor(window_1.performance && window_1.performance.now() || Date.now());
2008        this.data = {};
2009      }
2010
2011      var _proto = FakeWeakMap.prototype;
2012
2013      _proto.set = function set(key, value) {
2014        var access = key[this.vdata] || newGUID();
2015
2016        if (!key[this.vdata]) {
2017          key[this.vdata] = access;
2018        }
2019
2020        this.data[access] = value;
2021        return this;
2022      };
2023
2024      _proto.get = function get(key) {
2025        var access = key[this.vdata]; // we have data, return it
2026
2027        if (access) {
2028          return this.data[access];
2029        } // we don't have data, return nothing.
2030        // return undefined explicitly as that's the contract for this method
2031
2032
2033        log$1('We have no data for this element', key);
2034        return undefined;
2035      };
2036
2037      _proto.has = function has(key) {
2038        var access = key[this.vdata];
2039        return access in this.data;
2040      };
2041
2042      _proto["delete"] = function _delete(key) {
2043        var access = key[this.vdata];
2044
2045        if (access) {
2046          delete this.data[access];
2047          delete key[this.vdata];
2048        }
2049      };
2050
2051      return FakeWeakMap;
2052    }();
2053  }
2054  /**
2055   * Element Data Store.
2056   *
2057   * Allows for binding data to an element without putting it directly on the
2058   * element. Ex. Event listeners are stored here.
2059   * (also from jsninja.com, slightly modified and updated for closure compiler)
2060   *
2061   * @type {Object}
2062   * @private
2063   */
2064
2065
2066  var DomData = window_1.WeakMap ? new WeakMap() : new FakeWeakMap();
2067
2068  /**
2069   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
2070   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
2071   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
2072   * robust as jquery's, so there's probably some differences.
2073   *
2074   * @file events.js
2075   * @module events
2076   */
2077  /**
2078   * Clean up the listener cache and dispatchers
2079   *
2080   * @param {Element|Object} elem
2081   *        Element to clean up
2082   *
2083   * @param {string} type
2084   *        Type of event to clean up
2085   */
2086
2087  function _cleanUpEvents(elem, type) {
2088    if (!DomData.has(elem)) {
2089      return;
2090    }
2091
2092    var data = DomData.get(elem); // Remove the events of a particular type if there are none left
2093
2094    if (data.handlers[type].length === 0) {
2095      delete data.handlers[type]; // data.handlers[type] = null;
2096      // Setting to null was causing an error with data.handlers
2097      // Remove the meta-handler from the element
2098
2099      if (elem.removeEventListener) {
2100        elem.removeEventListener(type, data.dispatcher, false);
2101      } else if (elem.detachEvent) {
2102        elem.detachEvent('on' + type, data.dispatcher);
2103      }
2104    } // Remove the events object if there are no types left
2105
2106
2107    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
2108      delete data.handlers;
2109      delete data.dispatcher;
2110      delete data.disabled;
2111    } // Finally remove the element data if there is no data left
2112
2113
2114    if (Object.getOwnPropertyNames(data).length === 0) {
2115      DomData["delete"](elem);
2116    }
2117  }
2118  /**
2119   * Loops through an array of event types and calls the requested method for each type.
2120   *
2121   * @param {Function} fn
2122   *        The event method we want to use.
2123   *
2124   * @param {Element|Object} elem
2125   *        Element or object to bind listeners to
2126   *
2127   * @param {string} type
2128   *        Type of event to bind to.
2129   *
2130   * @param {EventTarget~EventListener} callback
2131   *        Event listener.
2132   */
2133
2134
2135  function _handleMultipleEvents(fn, elem, types, callback) {
2136    types.forEach(function (type) {
2137      // Call the event method for each one of the types
2138      fn(elem, type, callback);
2139    });
2140  }
2141  /**
2142   * Fix a native event to have standard property values
2143   *
2144   * @param {Object} event
2145   *        Event object to fix.
2146   *
2147   * @return {Object}
2148   *         Fixed event object.
2149   */
2150
2151
2152  function fixEvent(event) {
2153    if (event.fixed_) {
2154      return event;
2155    }
2156
2157    function returnTrue() {
2158      return true;
2159    }
2160
2161    function returnFalse() {
2162      return false;
2163    } // Test if fixing up is needed
2164    // Used to check if !event.stopPropagation instead of isPropagationStopped
2165    // But native events return true for stopPropagation, but don't have
2166    // other expected methods like isPropagationStopped. Seems to be a problem
2167    // with the Javascript Ninja code. So we're just overriding all events now.
2168
2169
2170    if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
2171      var old = event || window_1.event;
2172      event = {}; // Clone the old object so that we can modify the values event = {};
2173      // IE8 Doesn't like when you mess with native event properties
2174      // Firefox returns false for event.hasOwnProperty('type') and other props
2175      //  which makes copying more difficult.
2176      // TODO: Probably best to create a whitelist of event props
2177
2178      for (var key in old) {
2179        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
2180        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
2181        // and webkitMovementX/Y
2182        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
2183          // Chrome 32+ warns if you try to copy deprecated returnValue, but
2184          // we still want to if preventDefault isn't supported (IE8).
2185          if (!(key === 'returnValue' && old.preventDefault)) {
2186            event[key] = old[key];
2187          }
2188        }
2189      } // The event occurred on this element
2190
2191
2192      if (!event.target) {
2193        event.target = event.srcElement || document_1;
2194      } // Handle which other element the event is related to
2195
2196
2197      if (!event.relatedTarget) {
2198        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
2199      } // Stop the default browser action
2200
2201
2202      event.preventDefault = function () {
2203        if (old.preventDefault) {
2204          old.preventDefault();
2205        }
2206
2207        event.returnValue = false;
2208        old.returnValue = false;
2209        event.defaultPrevented = true;
2210      };
2211
2212      event.defaultPrevented = false; // Stop the event from bubbling
2213
2214      event.stopPropagation = function () {
2215        if (old.stopPropagation) {
2216          old.stopPropagation();
2217        }
2218
2219        event.cancelBubble = true;
2220        old.cancelBubble = true;
2221        event.isPropagationStopped = returnTrue;
2222      };
2223
2224      event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
2225
2226      event.stopImmediatePropagation = function () {
2227        if (old.stopImmediatePropagation) {
2228          old.stopImmediatePropagation();
2229        }
2230
2231        event.isImmediatePropagationStopped = returnTrue;
2232        event.stopPropagation();
2233      };
2234
2235      event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
2236
2237      if (event.clientX !== null && event.clientX !== undefined) {
2238        var doc = document_1.documentElement;
2239        var body = document_1.body;
2240        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
2241        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
2242      } // Handle key presses
2243
2244
2245      event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
2246      // 0 == left; 1 == middle; 2 == right
2247
2248      if (event.button !== null && event.button !== undefined) {
2249        // The following is disabled because it does not pass videojs-standard
2250        // and... yikes.
2251
2252        /* eslint-disable */
2253        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
2254        /* eslint-enable */
2255      }
2256    }
2257
2258    event.fixed_ = true; // Returns fixed-up instance
2259
2260    return event;
2261  }
2262  /**
2263   * Whether passive event listeners are supported
2264   */
2265
2266  var _supportsPassive;
2267
2268  var supportsPassive = function supportsPassive() {
2269    if (typeof _supportsPassive !== 'boolean') {
2270      _supportsPassive = false;
2271
2272      try {
2273        var opts = Object.defineProperty({}, 'passive', {
2274          get: function get() {
2275            _supportsPassive = true;
2276          }
2277        });
2278        window_1.addEventListener('test', null, opts);
2279        window_1.removeEventListener('test', null, opts);
2280      } catch (e) {// disregard
2281      }
2282    }
2283
2284    return _supportsPassive;
2285  };
2286  /**
2287   * Touch events Chrome expects to be passive
2288   */
2289
2290
2291  var passiveEvents = ['touchstart', 'touchmove'];
2292  /**
2293   * Add an event listener to element
2294   * It stores the handler function in a separate cache object
2295   * and adds a generic handler to the element's event,
2296   * along with a unique id (guid) to the element.
2297   *
2298   * @param {Element|Object} elem
2299   *        Element or object to bind listeners to
2300   *
2301   * @param {string|string[]} type
2302   *        Type of event to bind to.
2303   *
2304   * @param {EventTarget~EventListener} fn
2305   *        Event listener.
2306   */
2307
2308  function on(elem, type, fn) {
2309    if (Array.isArray(type)) {
2310      return _handleMultipleEvents(on, elem, type, fn);
2311    }
2312
2313    if (!DomData.has(elem)) {
2314      DomData.set(elem, {});
2315    }
2316
2317    var data = DomData.get(elem); // We need a place to store all our handler data
2318
2319    if (!data.handlers) {
2320      data.handlers = {};
2321    }
2322
2323    if (!data.handlers[type]) {
2324      data.handlers[type] = [];
2325    }
2326
2327    if (!fn.guid) {
2328      fn.guid = newGUID();
2329    }
2330
2331    data.handlers[type].push(fn);
2332
2333    if (!data.dispatcher) {
2334      data.disabled = false;
2335
2336      data.dispatcher = function (event, hash) {
2337        if (data.disabled) {
2338          return;
2339        }
2340
2341        event = fixEvent(event);
2342        var handlers = data.handlers[event.type];
2343
2344        if (handlers) {
2345          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
2346          var handlersCopy = handlers.slice(0);
2347
2348          for (var m = 0, n = handlersCopy.length; m < n; m++) {
2349            if (event.isImmediatePropagationStopped()) {
2350              break;
2351            } else {
2352              try {
2353                handlersCopy[m].call(elem, event, hash);
2354              } catch (e) {
2355                log$1.error(e);
2356              }
2357            }
2358          }
2359        }
2360      };
2361    }
2362
2363    if (data.handlers[type].length === 1) {
2364      if (elem.addEventListener) {
2365        var options = false;
2366
2367        if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
2368          options = {
2369            passive: true
2370          };
2371        }
2372
2373        elem.addEventListener(type, data.dispatcher, options);
2374      } else if (elem.attachEvent) {
2375        elem.attachEvent('on' + type, data.dispatcher);
2376      }
2377    }
2378  }
2379  /**
2380   * Removes event listeners from an element
2381   *
2382   * @param {Element|Object} elem
2383   *        Object to remove listeners from.
2384   *
2385   * @param {string|string[]} [type]
2386   *        Type of listener to remove. Don't include to remove all events from element.
2387   *
2388   * @param {EventTarget~EventListener} [fn]
2389   *        Specific listener to remove. Don't include to remove listeners for an event
2390   *        type.
2391   */
2392
2393  function off(elem, type, fn) {
2394    // Don't want to add a cache object through getElData if not needed
2395    if (!DomData.has(elem)) {
2396      return;
2397    }
2398
2399    var data = DomData.get(elem); // If no events exist, nothing to unbind
2400
2401    if (!data.handlers) {
2402      return;
2403    }
2404
2405    if (Array.isArray(type)) {
2406      return _handleMultipleEvents(off, elem, type, fn);
2407    } // Utility function
2408
2409
2410    var removeType = function removeType(el, t) {
2411      data.handlers[t] = [];
2412
2413      _cleanUpEvents(el, t);
2414    }; // Are we removing all bound events?
2415
2416
2417    if (type === undefined) {
2418      for (var t in data.handlers) {
2419        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
2420          removeType(elem, t);
2421        }
2422      }
2423
2424      return;
2425    }
2426
2427    var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
2428
2429    if (!handlers) {
2430      return;
2431    } // If no listener was provided, remove all listeners for type
2432
2433
2434    if (!fn) {
2435      removeType(elem, type);
2436      return;
2437    } // We're only removing a single handler
2438
2439
2440    if (fn.guid) {
2441      for (var n = 0; n < handlers.length; n++) {
2442        if (handlers[n].guid === fn.guid) {
2443          handlers.splice(n--, 1);
2444        }
2445      }
2446    }
2447
2448    _cleanUpEvents(elem, type);
2449  }
2450  /**
2451   * Trigger an event for an element
2452   *
2453   * @param {Element|Object} elem
2454   *        Element to trigger an event on
2455   *
2456   * @param {EventTarget~Event|string} event
2457   *        A string (the type) or an event object with a type attribute
2458   *
2459   * @param {Object} [hash]
2460   *        data hash to pass along with the event
2461   *
2462   * @return {boolean|undefined}
2463   *         Returns the opposite of `defaultPrevented` if default was
2464   *         prevented. Otherwise, returns `undefined`
2465   */
2466
2467  function trigger(elem, event, hash) {
2468    // Fetches element data and a reference to the parent (for bubbling).
2469    // Don't want to add a data object to cache for every parent,
2470    // so checking hasElData first.
2471    var elemData = DomData.has(elem) ? DomData.get(elem) : {};
2472    var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
2473    // handler;
2474    // If an event name was passed as a string, creates an event out of it
2475
2476    if (typeof event === 'string') {
2477      event = {
2478        type: event,
2479        target: elem
2480      };
2481    } else if (!event.target) {
2482      event.target = elem;
2483    } // Normalizes the event properties.
2484
2485
2486    event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
2487
2488    if (elemData.dispatcher) {
2489      elemData.dispatcher.call(elem, event, hash);
2490    } // Unless explicitly stopped or the event does not bubble (e.g. media events)
2491    // recursively calls this function to bubble the event up the DOM.
2492
2493
2494    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2495      trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
2496    } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2497      if (!DomData.has(event.target)) {
2498        DomData.set(event.target, {});
2499      }
2500
2501      var targetData = DomData.get(event.target); // Checks if the target has a default action for this event.
2502
2503      if (event.target[event.type]) {
2504        // Temporarily disables event dispatching on the target as we have already executed the handler.
2505        targetData.disabled = true; // Executes the default action.
2506
2507        if (typeof event.target[event.type] === 'function') {
2508          event.target[event.type]();
2509        } // Re-enables event dispatching.
2510
2511
2512        targetData.disabled = false;
2513      }
2514    } // Inform the triggerer if the default was prevented by returning false
2515
2516
2517    return !event.defaultPrevented;
2518  }
2519  /**
2520   * Trigger a listener only once for an event.
2521   *
2522   * @param {Element|Object} elem
2523   *        Element or object to bind to.
2524   *
2525   * @param {string|string[]} type
2526   *        Name/type of event
2527   *
2528   * @param {Event~EventListener} fn
2529   *        Event listener function
2530   */
2531
2532  function one(elem, type, fn) {
2533    if (Array.isArray(type)) {
2534      return _handleMultipleEvents(one, elem, type, fn);
2535    }
2536
2537    var func = function func() {
2538      off(elem, type, func);
2539      fn.apply(this, arguments);
2540    }; // copy the guid to the new function so it can removed using the original function's ID
2541
2542
2543    func.guid = fn.guid = fn.guid || newGUID();
2544    on(elem, type, func);
2545  }
2546  /**
2547   * Trigger a listener only once and then turn if off for all
2548   * configured events
2549   *
2550   * @param {Element|Object} elem
2551   *        Element or object to bind to.
2552   *
2553   * @param {string|string[]} type
2554   *        Name/type of event
2555   *
2556   * @param {Event~EventListener} fn
2557   *        Event listener function
2558   */
2559
2560  function any(elem, type, fn) {
2561    var func = function func() {
2562      off(elem, type, func);
2563      fn.apply(this, arguments);
2564    }; // copy the guid to the new function so it can removed using the original function's ID
2565
2566
2567    func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything
2568
2569    on(elem, type, func);
2570  }
2571
2572  var Events = /*#__PURE__*/Object.freeze({
2573    __proto__: null,
2574    fixEvent: fixEvent,
2575    on: on,
2576    off: off,
2577    trigger: trigger,
2578    one: one,
2579    any: any
2580  });
2581
2582  /**
2583   * @file fn.js
2584   * @module fn
2585   */
2586  var UPDATE_REFRESH_INTERVAL = 30;
2587  /**
2588   * Bind (a.k.a proxy or context). A simple method for changing the context of
2589   * a function.
2590   *
2591   * It also stores a unique id on the function so it can be easily removed from
2592   * events.
2593   *
2594   * @function
2595   * @param    {Mixed} context
2596   *           The object to bind as scope.
2597   *
2598   * @param    {Function} fn
2599   *           The function to be bound to a scope.
2600   *
2601   * @param    {number} [uid]
2602   *           An optional unique ID for the function to be set
2603   *
2604   * @return   {Function}
2605   *           The new function that will be bound into the context given
2606   */
2607
2608  var bind = function bind(context, fn, uid) {
2609    // Make sure the function has a unique ID
2610    if (!fn.guid) {
2611      fn.guid = newGUID();
2612    } // Create the new function that changes the context
2613
2614
2615    var bound = fn.bind(context); // Allow for the ability to individualize this function
2616    // Needed in the case where multiple objects might share the same prototype
2617    // IF both items add an event listener with the same function, then you try to remove just one
2618    // it will remove both because they both have the same guid.
2619    // when using this, you need to use the bind method when you remove the listener as well.
2620    // currently used in text tracks
2621
2622    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2623    return bound;
2624  };
2625  /**
2626   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2627   * at most once per every `wait` milliseconds.
2628   *
2629   * @function
2630   * @param    {Function} fn
2631   *           The function to be throttled.
2632   *
2633   * @param    {number}   wait
2634   *           The number of milliseconds by which to throttle.
2635   *
2636   * @return   {Function}
2637   */
2638
2639  var throttle = function throttle(fn, wait) {
2640    var last = window_1.performance.now();
2641
2642    var throttled = function throttled() {
2643      var now = window_1.performance.now();
2644
2645      if (now - last >= wait) {
2646        fn.apply(void 0, arguments);
2647        last = now;
2648      }
2649    };
2650
2651    return throttled;
2652  };
2653  /**
2654   * Creates a debounced function that delays invoking `func` until after `wait`
2655   * milliseconds have elapsed since the last time the debounced function was
2656   * invoked.
2657   *
2658   * Inspired by lodash and underscore implementations.
2659   *
2660   * @function
2661   * @param    {Function} func
2662   *           The function to wrap with debounce behavior.
2663   *
2664   * @param    {number} wait
2665   *           The number of milliseconds to wait after the last invocation.
2666   *
2667   * @param    {boolean} [immediate]
2668   *           Whether or not to invoke the function immediately upon creation.
2669   *
2670   * @param    {Object} [context=window]
2671   *           The "context" in which the debounced function should debounce. For
2672   *           example, if this function should be tied to a Video.js player,
2673   *           the player can be passed here. Alternatively, defaults to the
2674   *           global `window` object.
2675   *
2676   * @return   {Function}
2677   *           A debounced function.
2678   */
2679
2680  var debounce = function debounce(func, wait, immediate, context) {
2681    if (context === void 0) {
2682      context = window_1;
2683    }
2684
2685    var timeout;
2686
2687    var cancel = function cancel() {
2688      context.clearTimeout(timeout);
2689      timeout = null;
2690    };
2691    /* eslint-disable consistent-this */
2692
2693
2694    var debounced = function debounced() {
2695      var self = this;
2696      var args = arguments;
2697
2698      var _later = function later() {
2699        timeout = null;
2700        _later = null;
2701
2702        if (!immediate) {
2703          func.apply(self, args);
2704        }
2705      };
2706
2707      if (!timeout && immediate) {
2708        func.apply(self, args);
2709      }
2710
2711      context.clearTimeout(timeout);
2712      timeout = context.setTimeout(_later, wait);
2713    };
2714    /* eslint-enable consistent-this */
2715
2716
2717    debounced.cancel = cancel;
2718    return debounced;
2719  };
2720
2721  /**
2722   * @file src/js/event-target.js
2723   */
2724  /**
2725   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2726   * adds shorthand functions that wrap around lengthy functions. For example:
2727   * the `on` function is a wrapper around `addEventListener`.
2728   *
2729   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2730   * @class EventTarget
2731   */
2732
2733  var EventTarget$2 = function EventTarget() {};
2734  /**
2735   * A Custom DOM event.
2736   *
2737   * @typedef {Object} EventTarget~Event
2738   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2739   */
2740
2741  /**
2742   * All event listeners should follow the following format.
2743   *
2744   * @callback EventTarget~EventListener
2745   * @this {EventTarget}
2746   *
2747   * @param {EventTarget~Event} event
2748   *        the event that triggered this function
2749   *
2750   * @param {Object} [hash]
2751   *        hash of data sent during the event
2752   */
2753
2754  /**
2755   * An object containing event names as keys and booleans as values.
2756   *
2757   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2758   *         will have extra functionality. See that function for more information.
2759   *
2760   * @property EventTarget.prototype.allowedEvents_
2761   * @private
2762   */
2763
2764
2765  EventTarget$2.prototype.allowedEvents_ = {};
2766  /**
2767   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2768   * function that will get called when an event with a certain name gets triggered.
2769   *
2770   * @param {string|string[]} type
2771   *        An event name or an array of event names.
2772   *
2773   * @param {EventTarget~EventListener} fn
2774   *        The function to call with `EventTarget`s
2775   */
2776
2777  EventTarget$2.prototype.on = function (type, fn) {
2778    // Remove the addEventListener alias before calling Events.on
2779    // so we don't get into an infinite type loop
2780    var ael = this.addEventListener;
2781
2782    this.addEventListener = function () {};
2783
2784    on(this, type, fn);
2785    this.addEventListener = ael;
2786  };
2787  /**
2788   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2789   * the standard DOM API.
2790   *
2791   * @function
2792   * @see {@link EventTarget#on}
2793   */
2794
2795
2796  EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
2797  /**
2798   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2799   * This makes it so that the `event listener` will no longer get called when the
2800   * named event happens.
2801   *
2802   * @param {string|string[]} type
2803   *        An event name or an array of event names.
2804   *
2805   * @param {EventTarget~EventListener} fn
2806   *        The function to remove.
2807   */
2808
2809  EventTarget$2.prototype.off = function (type, fn) {
2810    off(this, type, fn);
2811  };
2812  /**
2813   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2814   * the standard DOM API.
2815   *
2816   * @function
2817   * @see {@link EventTarget#off}
2818   */
2819
2820
2821  EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
2822  /**
2823   * This function will add an `event listener` that gets triggered only once. After the
2824   * first trigger it will get removed. This is like adding an `event listener`
2825   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2826   *
2827   * @param {string|string[]} type
2828   *        An event name or an array of event names.
2829   *
2830   * @param {EventTarget~EventListener} fn
2831   *        The function to be called once for each event name.
2832   */
2833
2834  EventTarget$2.prototype.one = function (type, fn) {
2835    // Remove the addEventListener aliasing Events.on
2836    // so we don't get into an infinite type loop
2837    var ael = this.addEventListener;
2838
2839    this.addEventListener = function () {};
2840
2841    one(this, type, fn);
2842    this.addEventListener = ael;
2843  };
2844
2845  EventTarget$2.prototype.any = function (type, fn) {
2846    // Remove the addEventListener aliasing Events.on
2847    // so we don't get into an infinite type loop
2848    var ael = this.addEventListener;
2849
2850    this.addEventListener = function () {};
2851
2852    any(this, type, fn);
2853    this.addEventListener = ael;
2854  };
2855  /**
2856   * This function causes an event to happen. This will then cause any `event listeners`
2857   * that are waiting for that event, to get called. If there are no `event listeners`
2858   * for an event then nothing will happen.
2859   *
2860   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2861   * Trigger will also call the `on` + `uppercaseEventName` function.
2862   *
2863   * Example:
2864   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2865   * `onClick` if it exists.
2866   *
2867   * @param {string|EventTarget~Event|Object} event
2868   *        The name of the event, an `Event`, or an object with a key of type set to
2869   *        an event name.
2870   */
2871
2872
2873  EventTarget$2.prototype.trigger = function (event) {
2874    var type = event.type || event; // deprecation
2875    // In a future version we should default target to `this`
2876    // similar to how we default the target to `elem` in
2877    // `Events.trigger`. Right now the default `target` will be
2878    // `document` due to the `Event.fixEvent` call.
2879
2880    if (typeof event === 'string') {
2881      event = {
2882        type: type
2883      };
2884    }
2885
2886    event = fixEvent(event);
2887
2888    if (this.allowedEvents_[type] && this['on' + type]) {
2889      this['on' + type](event);
2890    }
2891
2892    trigger(this, event);
2893  };
2894  /**
2895   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2896   * the standard DOM API.
2897   *
2898   * @function
2899   * @see {@link EventTarget#trigger}
2900   */
2901
2902
2903  EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
2904  var EVENT_MAP;
2905
2906  EventTarget$2.prototype.queueTrigger = function (event) {
2907    var _this = this;
2908
2909    // only set up EVENT_MAP if it'll be used
2910    if (!EVENT_MAP) {
2911      EVENT_MAP = new Map();
2912    }
2913
2914    var type = event.type || event;
2915    var map = EVENT_MAP.get(this);
2916
2917    if (!map) {
2918      map = new Map();
2919      EVENT_MAP.set(this, map);
2920    }
2921
2922    var oldTimeout = map.get(type);
2923    map["delete"](type);
2924    window_1.clearTimeout(oldTimeout);
2925    var timeout = window_1.setTimeout(function () {
2926      // if we cleared out all timeouts for the current target, delete its map
2927      if (map.size === 0) {
2928        map = null;
2929        EVENT_MAP["delete"](_this);
2930      }
2931
2932      _this.trigger(event);
2933    }, 0);
2934    map.set(type, timeout);
2935  };
2936
2937  /**
2938   * @file mixins/evented.js
2939   * @module evented
2940   */
2941
2942  var objName = function objName(obj) {
2943    if (typeof obj.name === 'function') {
2944      return obj.name();
2945    }
2946
2947    if (typeof obj.name === 'string') {
2948      return obj.name;
2949    }
2950
2951    if (obj.name_) {
2952      return obj.name_;
2953    }
2954
2955    if (obj.constructor && obj.constructor.name) {
2956      return obj.constructor.name;
2957    }
2958
2959    return typeof obj;
2960  };
2961  /**
2962   * Returns whether or not an object has had the evented mixin applied.
2963   *
2964   * @param  {Object} object
2965   *         An object to test.
2966   *
2967   * @return {boolean}
2968   *         Whether or not the object appears to be evented.
2969   */
2970
2971
2972  var isEvented = function isEvented(object) {
2973    return object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2974      return typeof object[k] === 'function';
2975    });
2976  };
2977  /**
2978   * Adds a callback to run after the evented mixin applied.
2979   *
2980   * @param  {Object} object
2981   *         An object to Add
2982   * @param  {Function} callback
2983   *         The callback to run.
2984   */
2985
2986
2987  var addEventedCallback = function addEventedCallback(target, callback) {
2988    if (isEvented(target)) {
2989      callback();
2990    } else {
2991      if (!target.eventedCallbacks) {
2992        target.eventedCallbacks = [];
2993      }
2994
2995      target.eventedCallbacks.push(callback);
2996    }
2997  };
2998  /**
2999   * Whether a value is a valid event type - non-empty string or array.
3000   *
3001   * @private
3002   * @param  {string|Array} type
3003   *         The type value to test.
3004   *
3005   * @return {boolean}
3006   *         Whether or not the type is a valid event type.
3007   */
3008
3009
3010  var isValidEventType = function isValidEventType(type) {
3011    return (// The regex here verifies that the `type` contains at least one non-
3012      // whitespace character.
3013      typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
3014    );
3015  };
3016  /**
3017   * Validates a value to determine if it is a valid event target. Throws if not.
3018   *
3019   * @private
3020   * @throws {Error}
3021   *         If the target does not appear to be a valid event target.
3022   *
3023   * @param  {Object} target
3024   *         The object to test.
3025   *
3026   * @param  {Object} obj
3027   *         The evented object we are validating for
3028   *
3029   * @param  {string} fnName
3030   *         The name of the evented mixin function that called this.
3031   */
3032
3033
3034  var validateTarget = function validateTarget(target, obj, fnName) {
3035    if (!target || !target.nodeName && !isEvented(target)) {
3036      throw new Error("Invalid target for " + objName(obj) + "#" + fnName + "; must be a DOM node or evented object.");
3037    }
3038  };
3039  /**
3040   * Validates a value to determine if it is a valid event target. Throws if not.
3041   *
3042   * @private
3043   * @throws {Error}
3044   *         If the type does not appear to be a valid event type.
3045   *
3046   * @param  {string|Array} type
3047   *         The type to test.
3048   *
3049   * @param  {Object} obj
3050  *         The evented object we are validating for
3051   *
3052   * @param  {string} fnName
3053   *         The name of the evented mixin function that called this.
3054   */
3055
3056
3057  var validateEventType = function validateEventType(type, obj, fnName) {
3058    if (!isValidEventType(type)) {
3059      throw new Error("Invalid event type for " + objName(obj) + "#" + fnName + "; must be a non-empty string or array.");
3060    }
3061  };
3062  /**
3063   * Validates a value to determine if it is a valid listener. Throws if not.
3064   *
3065   * @private
3066   * @throws {Error}
3067   *         If the listener is not a function.
3068   *
3069   * @param  {Function} listener
3070   *         The listener to test.
3071   *
3072   * @param  {Object} obj
3073   *         The evented object we are validating for
3074   *
3075   * @param  {string} fnName
3076   *         The name of the evented mixin function that called this.
3077   */
3078
3079
3080  var validateListener = function validateListener(listener, obj, fnName) {
3081    if (typeof listener !== 'function') {
3082      throw new Error("Invalid listener for " + objName(obj) + "#" + fnName + "; must be a function.");
3083    }
3084  };
3085  /**
3086   * Takes an array of arguments given to `on()` or `one()`, validates them, and
3087   * normalizes them into an object.
3088   *
3089   * @private
3090   * @param  {Object} self
3091   *         The evented object on which `on()` or `one()` was called. This
3092   *         object will be bound as the `this` value for the listener.
3093   *
3094   * @param  {Array} args
3095   *         An array of arguments passed to `on()` or `one()`.
3096   *
3097   * @param  {string} fnName
3098   *         The name of the evented mixin function that called this.
3099   *
3100   * @return {Object}
3101   *         An object containing useful values for `on()` or `one()` calls.
3102   */
3103
3104
3105  var normalizeListenArgs = function normalizeListenArgs(self, args, fnName) {
3106    // If the number of arguments is less than 3, the target is always the
3107    // evented object itself.
3108    var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
3109    var target;
3110    var type;
3111    var listener;
3112
3113    if (isTargetingSelf) {
3114      target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
3115      // the evented object itself.
3116
3117      if (args.length >= 3) {
3118        args.shift();
3119      }
3120
3121      type = args[0];
3122      listener = args[1];
3123    } else {
3124      target = args[0];
3125      type = args[1];
3126      listener = args[2];
3127    }
3128
3129    validateTarget(target, self, fnName);
3130    validateEventType(type, self, fnName);
3131    validateListener(listener, self, fnName);
3132    listener = bind(self, listener);
3133    return {
3134      isTargetingSelf: isTargetingSelf,
3135      target: target,
3136      type: type,
3137      listener: listener
3138    };
3139  };
3140  /**
3141   * Adds the listener to the event type(s) on the target, normalizing for
3142   * the type of target.
3143   *
3144   * @private
3145   * @param  {Element|Object} target
3146   *         A DOM node or evented object.
3147   *
3148   * @param  {string} method
3149   *         The event binding method to use ("on" or "one").
3150   *
3151   * @param  {string|Array} type
3152   *         One or more event type(s).
3153   *
3154   * @param  {Function} listener
3155   *         A listener function.
3156   */
3157
3158
3159  var listen = function listen(target, method, type, listener) {
3160    validateTarget(target, target, method);
3161
3162    if (target.nodeName) {
3163      Events[method](target, type, listener);
3164    } else {
3165      target[method](type, listener);
3166    }
3167  };
3168  /**
3169   * Contains methods that provide event capabilities to an object which is passed
3170   * to {@link module:evented|evented}.
3171   *
3172   * @mixin EventedMixin
3173   */
3174
3175
3176  var EventedMixin = {
3177    /**
3178     * Add a listener to an event (or events) on this object or another evented
3179     * object.
3180     *
3181     * @param  {string|Array|Element|Object} targetOrType
3182     *         If this is a string or array, it represents the event type(s)
3183     *         that will trigger the listener.
3184     *
3185     *         Another evented object can be passed here instead, which will
3186     *         cause the listener to listen for events on _that_ object.
3187     *
3188     *         In either case, the listener's `this` value will be bound to
3189     *         this object.
3190     *
3191     * @param  {string|Array|Function} typeOrListener
3192     *         If the first argument was a string or array, this should be the
3193     *         listener function. Otherwise, this is a string or array of event
3194     *         type(s).
3195     *
3196     * @param  {Function} [listener]
3197     *         If the first argument was another evented object, this will be
3198     *         the listener function.
3199     */
3200    on: function on() {
3201      var _this = this;
3202
3203      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
3204        args[_key] = arguments[_key];
3205      }
3206
3207      var _normalizeListenArgs = normalizeListenArgs(this, args, 'on'),
3208          isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
3209          target = _normalizeListenArgs.target,
3210          type = _normalizeListenArgs.type,
3211          listener = _normalizeListenArgs.listener;
3212
3213      listen(target, 'on', type, listener); // If this object is listening to another evented object.
3214
3215      if (!isTargetingSelf) {
3216        // If this object is disposed, remove the listener.
3217        var removeListenerOnDispose = function removeListenerOnDispose() {
3218          return _this.off(target, type, listener);
3219        }; // Use the same function ID as the listener so we can remove it later it
3220        // using the ID of the original listener.
3221
3222
3223        removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
3224        // that if the target is disposed BEFORE this object, we remove the
3225        // removal listener that was just added. Otherwise, we create a memory leak.
3226
3227        var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
3228          return _this.off('dispose', removeListenerOnDispose);
3229        }; // Use the same function ID as the listener so we can remove it later
3230        // it using the ID of the original listener.
3231
3232
3233        removeRemoverOnTargetDispose.guid = listener.guid;
3234        listen(this, 'on', 'dispose', removeListenerOnDispose);
3235        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
3236      }
3237    },
3238
3239    /**
3240     * Add a listener to an event (or events) on this object or another evented
3241     * object. The listener will be called once per event and then removed.
3242     *
3243     * @param  {string|Array|Element|Object} targetOrType
3244     *         If this is a string or array, it represents the event type(s)
3245     *         that will trigger the listener.
3246     *
3247     *         Another evented object can be passed here instead, which will
3248     *         cause the listener to listen for events on _that_ object.
3249     *
3250     *         In either case, the listener's `this` value will be bound to
3251     *         this object.
3252     *
3253     * @param  {string|Array|Function} typeOrListener
3254     *         If the first argument was a string or array, this should be the
3255     *         listener function. Otherwise, this is a string or array of event
3256     *         type(s).
3257     *
3258     * @param  {Function} [listener]
3259     *         If the first argument was another evented object, this will be
3260     *         the listener function.
3261     */
3262    one: function one() {
3263      var _this2 = this;
3264
3265      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
3266        args[_key2] = arguments[_key2];
3267      }
3268
3269      var _normalizeListenArgs2 = normalizeListenArgs(this, args, 'one'),
3270          isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
3271          target = _normalizeListenArgs2.target,
3272          type = _normalizeListenArgs2.type,
3273          listener = _normalizeListenArgs2.listener; // Targeting this evented object.
3274
3275
3276      if (isTargetingSelf) {
3277        listen(target, 'one', type, listener); // Targeting another evented object.
3278      } else {
3279        // TODO: This wrapper is incorrect! It should only
3280        //       remove the wrapper for the event type that called it.
3281        //       Instead all listners are removed on the first trigger!
3282        //       see https://github.com/videojs/video.js/issues/5962
3283        var wrapper = function wrapper() {
3284          _this2.off(target, type, wrapper);
3285
3286          for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
3287            largs[_key3] = arguments[_key3];
3288          }
3289
3290          listener.apply(null, largs);
3291        }; // Use the same function ID as the listener so we can remove it later
3292        // it using the ID of the original listener.
3293
3294
3295        wrapper.guid = listener.guid;
3296        listen(target, 'one', type, wrapper);
3297      }
3298    },
3299
3300    /**
3301     * Add a listener to an event (or events) on this object or another evented
3302     * object. The listener will only be called once for the first event that is triggered
3303     * then removed.
3304     *
3305     * @param  {string|Array|Element|Object} targetOrType
3306     *         If this is a string or array, it represents the event type(s)
3307     *         that will trigger the listener.
3308     *
3309     *         Another evented object can be passed here instead, which will
3310     *         cause the listener to listen for events on _that_ object.
3311     *
3312     *         In either case, the listener's `this` value will be bound to
3313     *         this object.
3314     *
3315     * @param  {string|Array|Function} typeOrListener
3316     *         If the first argument was a string or array, this should be the
3317     *         listener function. Otherwise, this is a string or array of event
3318     *         type(s).
3319     *
3320     * @param  {Function} [listener]
3321     *         If the first argument was another evented object, this will be
3322     *         the listener function.
3323     */
3324    any: function any() {
3325      var _this3 = this;
3326
3327      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
3328        args[_key4] = arguments[_key4];
3329      }
3330
3331      var _normalizeListenArgs3 = normalizeListenArgs(this, args, 'any'),
3332          isTargetingSelf = _normalizeListenArgs3.isTargetingSelf,
3333          target = _normalizeListenArgs3.target,
3334          type = _normalizeListenArgs3.type,
3335          listener = _normalizeListenArgs3.listener; // Targeting this evented object.
3336
3337
3338      if (isTargetingSelf) {
3339        listen(target, 'any', type, listener); // Targeting another evented object.
3340      } else {
3341        var wrapper = function wrapper() {
3342          _this3.off(target, type, wrapper);
3343
3344          for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
3345            largs[_key5] = arguments[_key5];
3346          }
3347
3348          listener.apply(null, largs);
3349        }; // Use the same function ID as the listener so we can remove it later
3350        // it using the ID of the original listener.
3351
3352
3353        wrapper.guid = listener.guid;
3354        listen(target, 'any', type, wrapper);
3355      }
3356    },
3357
3358    /**
3359     * Removes listener(s) from event(s) on an evented object.
3360     *
3361     * @param  {string|Array|Element|Object} [targetOrType]
3362     *         If this is a string or array, it represents the event type(s).
3363     *
3364     *         Another evented object can be passed here instead, in which case
3365     *         ALL 3 arguments are _required_.
3366     *
3367     * @param  {string|Array|Function} [typeOrListener]
3368     *         If the first argument was a string or array, this may be the
3369     *         listener function. Otherwise, this is a string or array of event
3370     *         type(s).
3371     *
3372     * @param  {Function} [listener]
3373     *         If the first argument was another evented object, this will be
3374     *         the listener function; otherwise, _all_ listeners bound to the
3375     *         event type(s) will be removed.
3376     */
3377    off: function off$1(targetOrType, typeOrListener, listener) {
3378      // Targeting this evented object.
3379      if (!targetOrType || isValidEventType(targetOrType)) {
3380        off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
3381      } else {
3382        var target = targetOrType;
3383        var type = typeOrListener; // Fail fast and in a meaningful way!
3384
3385        validateTarget(target, this, 'off');
3386        validateEventType(type, this, 'off');
3387        validateListener(listener, this, 'off'); // Ensure there's at least a guid, even if the function hasn't been used
3388
3389        listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
3390        // the same guid as the event listener in on().
3391
3392        this.off('dispose', listener);
3393
3394        if (target.nodeName) {
3395          off(target, type, listener);
3396          off(target, 'dispose', listener);
3397        } else if (isEvented(target)) {
3398          target.off(type, listener);
3399          target.off('dispose', listener);
3400        }
3401      }
3402    },
3403
3404    /**
3405     * Fire an event on this evented object, causing its listeners to be called.
3406     *
3407     * @param   {string|Object} event
3408     *          An event type or an object with a type property.
3409     *
3410     * @param   {Object} [hash]
3411     *          An additional object to pass along to listeners.
3412     *
3413     * @return {boolean}
3414     *          Whether or not the default behavior was prevented.
3415     */
3416    trigger: function trigger$1(event, hash) {
3417      validateTarget(this.eventBusEl_, this, 'trigger');
3418      var type = event && typeof event !== 'string' ? event.type : event;
3419
3420      if (!isValidEventType(type)) {
3421        var error = "Invalid event type for " + objName(this) + "#trigger; " + 'must be a non-empty string or object with a type key that has a non-empty value.';
3422
3423        if (event) {
3424          (this.log || log$1).error(error);
3425        } else {
3426          throw new Error(error);
3427        }
3428      }
3429
3430      return trigger(this.eventBusEl_, event, hash);
3431    }
3432  };
3433  /**
3434   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
3435   *
3436   * @param  {Object} target
3437   *         The object to which to add event methods.
3438   *
3439   * @param  {Object} [options={}]
3440   *         Options for customizing the mixin behavior.
3441   *
3442   * @param  {string} [options.eventBusKey]
3443   *         By default, adds a `eventBusEl_` DOM element to the target object,
3444   *         which is used as an event bus. If the target object already has a
3445   *         DOM element that should be used, pass its key here.
3446   *
3447   * @return {Object}
3448   *         The target object.
3449   */
3450
3451  function evented(target, options) {
3452    if (options === void 0) {
3453      options = {};
3454    }
3455
3456    var _options = options,
3457        eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
3458
3459    if (eventBusKey) {
3460      if (!target[eventBusKey].nodeName) {
3461        throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
3462      }
3463
3464      target.eventBusEl_ = target[eventBusKey];
3465    } else {
3466      target.eventBusEl_ = createEl('span', {
3467        className: 'vjs-event-bus'
3468      });
3469    }
3470
3471    assign(target, EventedMixin);
3472
3473    if (target.eventedCallbacks) {
3474      target.eventedCallbacks.forEach(function (callback) {
3475        callback();
3476      });
3477    } // When any evented object is disposed, it removes all its listeners.
3478
3479
3480    target.on('dispose', function () {
3481      target.off();
3482      [target, target.el_, target.eventBusEl_].forEach(function (val) {
3483        if (val && DomData.has(val)) {
3484          DomData["delete"](val);
3485        }
3486      });
3487      window_1.setTimeout(function () {
3488        target.eventBusEl_ = null;
3489      }, 0);
3490    });
3491    return target;
3492  }
3493
3494  /**
3495   * @file mixins/stateful.js
3496   * @module stateful
3497   */
3498  /**
3499   * Contains methods that provide statefulness to an object which is passed
3500   * to {@link module:stateful}.
3501   *
3502   * @mixin StatefulMixin
3503   */
3504
3505  var StatefulMixin = {
3506    /**
3507     * A hash containing arbitrary keys and values representing the state of
3508     * the object.
3509     *
3510     * @type {Object}
3511     */
3512    state: {},
3513
3514    /**
3515     * Set the state of an object by mutating its
3516     * {@link module:stateful~StatefulMixin.state|state} object in place.
3517     *
3518     * @fires   module:stateful~StatefulMixin#statechanged
3519     * @param   {Object|Function} stateUpdates
3520     *          A new set of properties to shallow-merge into the plugin state.
3521     *          Can be a plain object or a function returning a plain object.
3522     *
3523     * @return {Object|undefined}
3524     *          An object containing changes that occurred. If no changes
3525     *          occurred, returns `undefined`.
3526     */
3527    setState: function setState(stateUpdates) {
3528      var _this = this;
3529
3530      // Support providing the `stateUpdates` state as a function.
3531      if (typeof stateUpdates === 'function') {
3532        stateUpdates = stateUpdates();
3533      }
3534
3535      var changes;
3536      each(stateUpdates, function (value, key) {
3537        // Record the change if the value is different from what's in the
3538        // current state.
3539        if (_this.state[key] !== value) {
3540          changes = changes || {};
3541          changes[key] = {
3542            from: _this.state[key],
3543            to: value
3544          };
3545        }
3546
3547        _this.state[key] = value;
3548      }); // Only trigger "statechange" if there were changes AND we have a trigger
3549      // function. This allows us to not require that the target object be an
3550      // evented object.
3551
3552      if (changes && isEvented(this)) {
3553        /**
3554         * An event triggered on an object that is both
3555         * {@link module:stateful|stateful} and {@link module:evented|evented}
3556         * indicating that its state has changed.
3557         *
3558         * @event    module:stateful~StatefulMixin#statechanged
3559         * @type     {Object}
3560         * @property {Object} changes
3561         *           A hash containing the properties that were changed and
3562         *           the values they were changed `from` and `to`.
3563         */
3564        this.trigger({
3565          changes: changes,
3566          type: 'statechanged'
3567        });
3568      }
3569
3570      return changes;
3571    }
3572  };
3573  /**
3574   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3575   * object.
3576   *
3577   * If the target object is {@link module:evented|evented} and has a
3578   * `handleStateChanged` method, that method will be automatically bound to the
3579   * `statechanged` event on itself.
3580   *
3581   * @param   {Object} target
3582   *          The object to be made stateful.
3583   *
3584   * @param   {Object} [defaultState]
3585   *          A default set of properties to populate the newly-stateful object's
3586   *          `state` property.
3587   *
3588   * @return {Object}
3589   *          Returns the `target`.
3590   */
3591
3592  function stateful(target, defaultState) {
3593    assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
3594    // added in that step.
3595
3596    target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
3597
3598    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3599      target.on('statechanged', target.handleStateChanged);
3600    }
3601
3602    return target;
3603  }
3604
3605  /**
3606   * @file string-cases.js
3607   * @module to-lower-case
3608   */
3609
3610  /**
3611   * Lowercase the first letter of a string.
3612   *
3613   * @param {string} string
3614   *        String to be lowercased
3615   *
3616   * @return {string}
3617   *         The string with a lowercased first letter
3618   */
3619  var toLowerCase = function toLowerCase(string) {
3620    if (typeof string !== 'string') {
3621      return string;
3622    }
3623
3624    return string.replace(/./, function (w) {
3625      return w.toLowerCase();
3626    });
3627  };
3628  /**
3629   * Uppercase the first letter of a string.
3630   *
3631   * @param {string} string
3632   *        String to be uppercased
3633   *
3634   * @return {string}
3635   *         The string with an uppercased first letter
3636   */
3637
3638  var toTitleCase$1 = function toTitleCase(string) {
3639    if (typeof string !== 'string') {
3640      return string;
3641    }
3642
3643    return string.replace(/./, function (w) {
3644      return w.toUpperCase();
3645    });
3646  };
3647  /**
3648   * Compares the TitleCase versions of the two strings for equality.
3649   *
3650   * @param {string} str1
3651   *        The first string to compare
3652   *
3653   * @param {string} str2
3654   *        The second string to compare
3655   *
3656   * @return {boolean}
3657   *         Whether the TitleCase versions of the strings are equal
3658   */
3659
3660  var titleCaseEquals = function titleCaseEquals(str1, str2) {
3661    return toTitleCase$1(str1) === toTitleCase$1(str2);
3662  };
3663
3664  /**
3665   * @file merge-options.js
3666   * @module merge-options
3667   */
3668  /**
3669   * Merge two objects recursively.
3670   *
3671   * Performs a deep merge like
3672   * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
3673   * plain objects (not arrays, elements, or anything else).
3674   *
3675   * Non-plain object values will be copied directly from the right-most
3676   * argument.
3677   *
3678   * @static
3679   * @param   {Object[]} sources
3680   *          One or more objects to merge into a new object.
3681   *
3682   * @return {Object}
3683   *          A new object that is the merged result of all sources.
3684   */
3685
3686  function mergeOptions$3() {
3687    var result = {};
3688
3689    for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
3690      sources[_key] = arguments[_key];
3691    }
3692
3693    sources.forEach(function (source) {
3694      if (!source) {
3695        return;
3696      }
3697
3698      each(source, function (value, key) {
3699        if (!isPlain(value)) {
3700          result[key] = value;
3701          return;
3702        }
3703
3704        if (!isPlain(result[key])) {
3705          result[key] = {};
3706        }
3707
3708        result[key] = mergeOptions$3(result[key], value);
3709      });
3710    });
3711    return result;
3712  }
3713
3714  var MapSham = /*#__PURE__*/function () {
3715    function MapSham() {
3716      this.map_ = {};
3717    }
3718
3719    var _proto = MapSham.prototype;
3720
3721    _proto.has = function has(key) {
3722      return key in this.map_;
3723    };
3724
3725    _proto["delete"] = function _delete(key) {
3726      var has = this.has(key);
3727      delete this.map_[key];
3728      return has;
3729    };
3730
3731    _proto.set = function set(key, value) {
3732      this.map_[key] = value;
3733      return this;
3734    };
3735
3736    _proto.forEach = function forEach(callback, thisArg) {
3737      for (var key in this.map_) {
3738        callback.call(thisArg, this.map_[key], key, this);
3739      }
3740    };
3741
3742    return MapSham;
3743  }();
3744
3745  var Map$1 = window_1.Map ? window_1.Map : MapSham;
3746
3747  var SetSham = /*#__PURE__*/function () {
3748    function SetSham() {
3749      this.set_ = {};
3750    }
3751
3752    var _proto = SetSham.prototype;
3753
3754    _proto.has = function has(key) {
3755      return key in this.set_;
3756    };
3757
3758    _proto["delete"] = function _delete(key) {
3759      var has = this.has(key);
3760      delete this.set_[key];
3761      return has;
3762    };
3763
3764    _proto.add = function add(key) {
3765      this.set_[key] = 1;
3766      return this;
3767    };
3768
3769    _proto.forEach = function forEach(callback, thisArg) {
3770      for (var key in this.set_) {
3771        callback.call(thisArg, key, key, this);
3772      }
3773    };
3774
3775    return SetSham;
3776  }();
3777
3778  var Set = window_1.Set ? window_1.Set : SetSham;
3779
3780  /**
3781   * Player Component - Base class for all UI objects
3782   *
3783   * @file component.js
3784   */
3785  /**
3786   * Base class for all UI Components.
3787   * Components are UI objects which represent both a javascript object and an element
3788   * in the DOM. They can be children of other components, and can have
3789   * children themselves.
3790   *
3791   * Components can also use methods from {@link EventTarget}
3792   */
3793
3794  var Component$1 = /*#__PURE__*/function () {
3795    /**
3796     * A callback that is called when a component is ready. Does not have any
3797     * paramters and any callback value will be ignored.
3798     *
3799     * @callback Component~ReadyCallback
3800     * @this Component
3801     */
3802
3803    /**
3804     * Creates an instance of this class.
3805     *
3806     * @param {Player} player
3807     *        The `Player` that this class should be attached to.
3808     *
3809     * @param {Object} [options]
3810     *        The key/value store of player options.
3811     *
3812     * @param {Object[]} [options.children]
3813     *        An array of children objects to intialize this component with. Children objects have
3814     *        a name property that will be used if more than one component of the same type needs to be
3815     *        added.
3816     *
3817     * @param {Component~ReadyCallback} [ready]
3818     *        Function that gets called when the `Component` is ready.
3819     */
3820    function Component(player, options, ready) {
3821      // The component might be the player itself and we can't pass `this` to super
3822      if (!player && this.play) {
3823        this.player_ = player = this; // eslint-disable-line
3824      } else {
3825        this.player_ = player;
3826      }
3827
3828      this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method
3829
3830      this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
3831
3832      this.options_ = mergeOptions$3({}, this.options_); // Updated options with supplied options
3833
3834      options = this.options_ = mergeOptions$3(this.options_, options); // Get ID from options or options element if one is supplied
3835
3836      this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
3837
3838      if (!this.id_) {
3839        // Don't require the player ID function in the case of mock players
3840        var id = player && player.id && player.id() || 'no_player';
3841        this.id_ = id + "_component_" + newGUID();
3842      }
3843
3844      this.name_ = options.name || null; // Create element if one wasn't provided in options
3845
3846      if (options.el) {
3847        this.el_ = options.el;
3848      } else if (options.createEl !== false) {
3849        this.el_ = this.createEl();
3850      } // if evented is anything except false, we want to mixin in evented
3851
3852
3853      if (options.evented !== false) {
3854        // Make this an evented object and use `el_`, if available, as its event bus
3855        evented(this, {
3856          eventBusKey: this.el_ ? 'el_' : null
3857        });
3858        this.handleLanguagechange = this.handleLanguagechange.bind(this);
3859        this.on(this.player_, 'languagechange', this.handleLanguagechange);
3860      }
3861
3862      stateful(this, this.constructor.defaultState);
3863      this.children_ = [];
3864      this.childIndex_ = {};
3865      this.childNameIndex_ = {};
3866      this.setTimeoutIds_ = new Set();
3867      this.setIntervalIds_ = new Set();
3868      this.rafIds_ = new Set();
3869      this.namedRafs_ = new Map$1();
3870      this.clearingTimersOnDispose_ = false; // Add any child components in options
3871
3872      if (options.initChildren !== false) {
3873        this.initChildren();
3874      } // Don't want to trigger ready here or it will go before init is actually
3875      // finished for all children that run this constructor
3876
3877
3878      this.ready(ready);
3879
3880      if (options.reportTouchActivity !== false) {
3881        this.enableTouchActivity();
3882      }
3883    }
3884    /**
3885     * Dispose of the `Component` and all child components.
3886     *
3887     * @fires Component#dispose
3888     */
3889
3890
3891    var _proto = Component.prototype;
3892
3893    _proto.dispose = function dispose() {
3894      // Bail out if the component has already been disposed.
3895      if (this.isDisposed_) {
3896        return;
3897      }
3898
3899      if (this.readyQueue_) {
3900        this.readyQueue_.length = 0;
3901      }
3902      /**
3903       * Triggered when a `Component` is disposed.
3904       *
3905       * @event Component#dispose
3906       * @type {EventTarget~Event}
3907       *
3908       * @property {boolean} [bubbles=false]
3909       *           set to false so that the dispose event does not
3910       *           bubble up
3911       */
3912
3913
3914      this.trigger({
3915        type: 'dispose',
3916        bubbles: false
3917      });
3918      this.isDisposed_ = true; // Dispose all children.
3919
3920      if (this.children_) {
3921        for (var i = this.children_.length - 1; i >= 0; i--) {
3922          if (this.children_[i].dispose) {
3923            this.children_[i].dispose();
3924          }
3925        }
3926      } // Delete child references
3927
3928
3929      this.children_ = null;
3930      this.childIndex_ = null;
3931      this.childNameIndex_ = null;
3932      this.parentComponent_ = null;
3933
3934      if (this.el_) {
3935        // Remove element from DOM
3936        if (this.el_.parentNode) {
3937          this.el_.parentNode.removeChild(this.el_);
3938        }
3939
3940        this.el_ = null;
3941      } // remove reference to the player after disposing of the element
3942
3943
3944      this.player_ = null;
3945    }
3946    /**
3947     * Determine whether or not this component has been disposed.
3948     *
3949     * @return {boolean}
3950     *         If the component has been disposed, will be `true`. Otherwise, `false`.
3951     */
3952    ;
3953
3954    _proto.isDisposed = function isDisposed() {
3955      return Boolean(this.isDisposed_);
3956    }
3957    /**
3958     * Return the {@link Player} that the `Component` has attached to.
3959     *
3960     * @return {Player}
3961     *         The player that this `Component` has attached to.
3962     */
3963    ;
3964
3965    _proto.player = function player() {
3966      return this.player_;
3967    }
3968    /**
3969     * Deep merge of options objects with new options.
3970     * > Note: When both `obj` and `options` contain properties whose values are objects.
3971     *         The two properties get merged using {@link module:mergeOptions}
3972     *
3973     * @param {Object} obj
3974     *        The object that contains new options.
3975     *
3976     * @return {Object}
3977     *         A new object of `this.options_` and `obj` merged together.
3978     */
3979    ;
3980
3981    _proto.options = function options(obj) {
3982      if (!obj) {
3983        return this.options_;
3984      }
3985
3986      this.options_ = mergeOptions$3(this.options_, obj);
3987      return this.options_;
3988    }
3989    /**
3990     * Get the `Component`s DOM element
3991     *
3992     * @return {Element}
3993     *         The DOM element for this `Component`.
3994     */
3995    ;
3996
3997    _proto.el = function el() {
3998      return this.el_;
3999    }
4000    /**
4001     * Create the `Component`s DOM element.
4002     *
4003     * @param {string} [tagName]
4004     *        Element's DOM node type. e.g. 'div'
4005     *
4006     * @param {Object} [properties]
4007     *        An object of properties that should be set.
4008     *
4009     * @param {Object} [attributes]
4010     *        An object of attributes that should be set.
4011     *
4012     * @return {Element}
4013     *         The element that gets created.
4014     */
4015    ;
4016
4017    _proto.createEl = function createEl$1(tagName, properties, attributes) {
4018      return createEl(tagName, properties, attributes);
4019    }
4020    /**
4021     * Localize a string given the string in english.
4022     *
4023     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
4024     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
4025     *
4026     * If a `defaultValue` is provided, it'll use that over `string`,
4027     * if a value isn't found in provided language files.
4028     * This is useful if you want to have a descriptive key for token replacement
4029     * but have a succinct localized string and not require `en.json` to be included.
4030     *
4031     * Currently, it is used for the progress bar timing.
4032     * ```js
4033     * {
4034     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
4035     * }
4036     * ```
4037     * It is then used like so:
4038     * ```js
4039     * this.localize('progress bar timing: currentTime={1} duration{2}',
4040     *               [this.player_.currentTime(), this.player_.duration()],
4041     *               '{1} of {2}');
4042     * ```
4043     *
4044     * Which outputs something like: `01:23 of 24:56`.
4045     *
4046     *
4047     * @param {string} string
4048     *        The string to localize and the key to lookup in the language files.
4049     * @param {string[]} [tokens]
4050     *        If the current item has token replacements, provide the tokens here.
4051     * @param {string} [defaultValue]
4052     *        Defaults to `string`. Can be a default value to use for token replacement
4053     *        if the lookup key is needed to be separate.
4054     *
4055     * @return {string}
4056     *         The localized string or if no localization exists the english string.
4057     */
4058    ;
4059
4060    _proto.localize = function localize(string, tokens, defaultValue) {
4061      if (defaultValue === void 0) {
4062        defaultValue = string;
4063      }
4064
4065      var code = this.player_.language && this.player_.language();
4066      var languages = this.player_.languages && this.player_.languages();
4067      var language = languages && languages[code];
4068      var primaryCode = code && code.split('-')[0];
4069      var primaryLang = languages && languages[primaryCode];
4070      var localizedString = defaultValue;
4071
4072      if (language && language[string]) {
4073        localizedString = language[string];
4074      } else if (primaryLang && primaryLang[string]) {
4075        localizedString = primaryLang[string];
4076      }
4077
4078      if (tokens) {
4079        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
4080          var value = tokens[index - 1];
4081          var ret = value;
4082
4083          if (typeof value === 'undefined') {
4084            ret = match;
4085          }
4086
4087          return ret;
4088        });
4089      }
4090
4091      return localizedString;
4092    }
4093    /**
4094     * Handles language change for the player in components. Should be overriden by sub-components.
4095     *
4096     * @abstract
4097     */
4098    ;
4099
4100    _proto.handleLanguagechange = function handleLanguagechange() {}
4101    /**
4102     * Return the `Component`s DOM element. This is where children get inserted.
4103     * This will usually be the the same as the element returned in {@link Component#el}.
4104     *
4105     * @return {Element}
4106     *         The content element for this `Component`.
4107     */
4108    ;
4109
4110    _proto.contentEl = function contentEl() {
4111      return this.contentEl_ || this.el_;
4112    }
4113    /**
4114     * Get this `Component`s ID
4115     *
4116     * @return {string}
4117     *         The id of this `Component`
4118     */
4119    ;
4120
4121    _proto.id = function id() {
4122      return this.id_;
4123    }
4124    /**
4125     * Get the `Component`s name. The name gets used to reference the `Component`
4126     * and is set during registration.
4127     *
4128     * @return {string}
4129     *         The name of this `Component`.
4130     */
4131    ;
4132
4133    _proto.name = function name() {
4134      return this.name_;
4135    }
4136    /**
4137     * Get an array of all child components
4138     *
4139     * @return {Array}
4140     *         The children
4141     */
4142    ;
4143
4144    _proto.children = function children() {
4145      return this.children_;
4146    }
4147    /**
4148     * Returns the child `Component` with the given `id`.
4149     *
4150     * @param {string} id
4151     *        The id of the child `Component` to get.
4152     *
4153     * @return {Component|undefined}
4154     *         The child `Component` with the given `id` or undefined.
4155     */
4156    ;
4157
4158    _proto.getChildById = function getChildById(id) {
4159      return this.childIndex_[id];
4160    }
4161    /**
4162     * Returns the child `Component` with the given `name`.
4163     *
4164     * @param {string} name
4165     *        The name of the child `Component` to get.
4166     *
4167     * @return {Component|undefined}
4168     *         The child `Component` with the given `name` or undefined.
4169     */
4170    ;
4171
4172    _proto.getChild = function getChild(name) {
4173      if (!name) {
4174        return;
4175      }
4176
4177      return this.childNameIndex_[name];
4178    }
4179    /**
4180     * Returns the descendant `Component` following the givent
4181     * descendant `names`. For instance ['foo', 'bar', 'baz'] would
4182     * try to get 'foo' on the current component, 'bar' on the 'foo'
4183     * component and 'baz' on the 'bar' component and return undefined
4184     * if any of those don't exist.
4185     *
4186     * @param {...string[]|...string} names
4187     *        The name of the child `Component` to get.
4188     *
4189     * @return {Component|undefined}
4190     *         The descendant `Component` following the given desce
4190ndant
4191     *         `names` or undefined.
4192     */
4193    ;
4194
4195    _proto.getDescendant = function getDescendant() {
4196      for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) {
4197        names[_key] = arguments[_key];
4198      }
4199
4200      // flatten array argument into the main array
4201      names = names.reduce(function (acc, n) {
4202        return acc.concat(n);
4203      }, []);
4204      var currentChild = this;
4205
4206      for (var i = 0; i < names.length; i++) {
4207        currentChild = currentChild.getChild(names[i]);
4208
4209        if (!currentChild || !currentChild.getChild) {
4210          return;
4211        }
4212      }
4213
4214      return currentChild;
4215    }
4216    /**
4217     * Add a child `Component` inside the current `Component`.
4218     *
4219     *
4220     * @param {string|Component} child
4221     *        The name or instance of a child to add.
4222     *
4223     * @param {Object} [options={}]
4224     *        The key/value store of options that will get passed to children of
4225     *        the child.
4226     *
4227     * @param {number} [index=this.children_.length]
4228     *        The index to attempt to add a child into.
4229     *
4230     * @return {Component}
4231     *         The `Component` that gets added as a child. When using a string the
4232     *         `Component` will get created by this process.
4233     */
4234    ;
4235
4236    _proto.addChild = function addChild(child, options, index) {
4237      if (options === void 0) {
4238        options = {};
4239      }
4240
4241      if (index === void 0) {
4242        index = this.children_.length;
4243      }
4244
4245      var component;
4246      var componentName; // If child is a string, create component with options
4247
4248      if (typeof child === 'string') {
4249        componentName = toTitleCase$1(child);
4250        var componentClassName = options.componentClass || componentName; // Set name through options
4251
4252        options.name = componentName; // Create a new object & element for this controls set
4253        // If there's no .player_, this is a player
4254
4255        var ComponentClass = Component.getComponent(componentClassName);
4256
4257        if (!ComponentClass) {
4258          throw new Error("Component " + componentClassName + " does not exist");
4259        } // data stored directly on the videojs object may be
4260        // misidentified as a component to retain
4261        // backwards-compatibility with 4.x. check to make sure the
4262        // component class can be instantiated.
4263
4264
4265        if (typeof ComponentClass !== 'function') {
4266          return null;
4267        }
4268
4269        component = new ComponentClass(this.player_ || this, options); // child is a component instance
4270      } else {
4271        component = child;
4272      }
4273
4274      if (component.parentComponent_) {
4275        component.parentComponent_.removeChild(component);
4276      }
4277
4278      this.children_.splice(index, 0, component);
4279      component.parentComponent_ = this;
4280
4281      if (typeof component.id === 'function') {
4282        this.childIndex_[component.id()] = component;
4283      } // If a name wasn't used to create the component, check if we can use the
4284      // name function of the component
4285
4286
4287      componentName = componentName || component.name && toTitleCase$1(component.name());
4288
4289      if (componentName) {
4290        this.childNameIndex_[componentName] = component;
4291        this.childNameIndex_[toLowerCase(componentName)] = component;
4292      } // Add the UI object's element to the container div (box)
4293      // Having an element is not required
4294
4295
4296      if (typeof component.el === 'function' && component.el()) {
4297        // If inserting before a component, insert before that component's element
4298        var refNode = null;
4299
4300        if (this.children_[index + 1]) {
4301          // Most children are components, but the video tech is an HTML element
4302          if (this.children_[index + 1].el_) {
4303            refNode = this.children_[index + 1].el_;
4304          } else if (isEl(this.children_[index + 1])) {
4305            refNode = this.children_[index + 1];
4306          }
4307        }
4308
4309        this.contentEl().insertBefore(component.el(), refNode);
4310      } // Return so it can stored on parent object if desired.
4311
4312
4313      return component;
4314    }
4315    /**
4316     * Remove a child `Component` from this `Component`s list of children. Also removes
4317     * the child `Component`s element from this `Component`s element.
4318     *
4319     * @param {Component} component
4320     *        The child `Component` to remove.
4321     */
4322    ;
4323
4324    _proto.removeChild = function removeChild(component) {
4325      if (typeof component === 'string') {
4326        component = this.getChild(component);
4327      }
4328
4329      if (!component || !this.children_) {
4330        return;
4331      }
4332
4333      var childFound = false;
4334
4335      for (var i = this.children_.length - 1; i >= 0; i--) {
4336        if (this.children_[i] === component) {
4337          childFound = true;
4338          this.children_.splice(i, 1);
4339          break;
4340        }
4341      }
4342
4343      if (!childFound) {
4344        return;
4345      }
4346
4347      component.parentComponent_ = null;
4348      this.childIndex_[component.id()] = null;
4349      this.childNameIndex_[toTitleCase$1(component.name())] = null;
4350      this.childNameIndex_[toLowerCase(component.name())] = null;
4351      var compEl = component.el();
4352
4353      if (compEl && compEl.parentNode === this.contentEl()) {
4354        this.contentEl().removeChild(component.el());
4355      }
4356    }
4357    /**
4358     * Add and initialize default child `Component`s based upon options.
4359     */
4360    ;
4361
4362    _proto.initChildren = function initChildren() {
4363      var _this = this;
4364
4365      var children = this.options_.children;
4366
4367      if (children) {
4368        // `this` is `parent`
4369        var parentOptions = this.options_;
4370
4371        var handleAdd = function handleAdd(child) {
4372          var name = child.name;
4373          var opts = child.opts; // Allow options for children to be set at the parent options
4374          // e.g. videojs(id, { controlBar: false });
4375          // instead of videojs(id, { children: { controlBar: false });
4376
4377          if (parentOptions[name] !== undefined) {
4378            opts = parentOptions[name];
4379          } // Allow for disabling default components
4380          // e.g. options['children']['posterImage'] = false
4381
4382
4383          if (opts === false) {
4384            return;
4385          } // Allow options to be passed as a simple boolean if no configuration
4386          // is necessary.
4387
4388
4389          if (opts === true) {
4390            opts = {};
4391          } // We also want to pass the original player options
4392          // to each component as well so they don't need to
4393          // reach back into the player for options later.
4394
4395
4396          opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
4397          // Add a direct reference to the child by name on the parent instance.
4398          // If two of the same component are used, different names should be supplied
4399          // for each
4400
4401          var newChild = _this.addChild(name, opts);
4402
4403          if (newChild) {
4404            _this[name] = newChild;
4405          }
4406        }; // Allow for an array of children details to passed in the options
4407
4408
4409        var workingChildren;
4410        var Tech = Component.getComponent('Tech');
4411
4412        if (Array.isArray(children)) {
4413          workingChildren = children;
4414        } else {
4415          workingChildren = Object.keys(children);
4416        }
4417
4418        workingChildren // children that are in this.options_ but also in workingChildren  would
4419        // give us extra children we do not want. So, we want to filter them out.
4420        .concat(Object.keys(this.options_).filter(function (child) {
4421          return !workingChildren.some(function (wchild) {
4422            if (typeof wchild === 'string') {
4423              return child === wchild;
4424            }
4425
4426            return child === wchild.name;
4427          });
4428        })).map(function (child) {
4429          var name;
4430          var opts;
4431
4432          if (typeof child === 'string') {
4433            name = child;
4434            opts = children[name] || _this.options_[name] || {};
4435          } else {
4436            name = child.name;
4437            opts = child;
4438          }
4439
4440          return {
4441            name: name,
4442            opts: opts
4443          };
4444        }).filter(function (child) {
4445          // we have to make sure that child.name isn't in the techOrder since
4446          // techs are registerd as Components but can't aren't compatible
4447          // See https://github.com/videojs/video.js/issues/2772
4448          var c = Component.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
4449          return c && !Tech.isTech(c);
4450        }
4450).forEach(handleAdd);
4451      }
4452    }
4453    /**
4454     * Builds the default DOM class name. Should be overriden by sub-components.
4455     *
4456     * @return {string}
4457     *         The DOM class name for this object.
4458     *
4459     * @abstract
4460     */
4461    ;
4462
4463    _proto.buildCSSClass = function buildCSSClass() {
4464      // Child classes can include a function that does:
4465      // return 'CLASS NAME' + this._super();
4466      return '';
4467    }
4468    /**
4469     * Bind a listener to the component's ready state.
4470     * Different from event listeners in that if the ready event has already happened
4471     * it will trigger the function immediately.
4472     *
4473     * @return {Component}
4474     *         Returns itself; method can be chained.
4475     */
4476    ;
4477
4478    _proto.ready = function ready(fn, sync) {
4479      if (sync === void 0) {
4480        sync = false;
4481      }
4482
4483      if (!fn) {
4484        return;
4485      }
4486
4487      if (!this.isReady_) {
4488        this.readyQueue_ = this.readyQueue_ || [];
4489        this.readyQueue_.push(fn);
4490        return;
4491      }
4492
4493      if (sync) {
4494        fn.call(this);
4495      } else {
4496        // Call the function asynchronously by default for consistency
4497        this.setTimeout(fn, 1);
4498      }
4499    }
4500    /**
4501     * Trigger all the ready listeners for this `Component`.
4502     *
4503     * @fires Component#ready
4504     */
4505    ;
4506
4507    _proto.triggerReady = function triggerReady() {
4508      this.isReady_ = true; // Ensure ready is triggered asynchronously
4509
4510      this.setTimeout(function () {
4511        var readyQueue = this.readyQueue_; // Reset Ready Queue
4512
4513        this.readyQueue_ = [];
4514
4515        if (readyQueue && readyQueue.length > 0) {
4516          readyQueue.forEach(function (fn) {
4517            fn.call(this);
4518          }, this);
4519        } // Allow for using event listeners also
4520
4521        /**
4522         * Triggered when a `Component` is ready.
4523         *
4524         * @event Component#ready
4525         * @type {EventTarget~Event}
4526         */
4527
4528
4529        this.trigger('ready');
4530      }, 1);
4531    }
4532    /**
4533     * Find a single DOM element matching a `selector`. This can be within the `Component`s
4534     * `contentEl()` or another custom context.
4535     *
4536     * @param {string} selector
4537     *        A valid CSS selector, which will be passed to `querySelector`.
4538     *
4539     * @param {Element|string} [context=this.contentEl()]
4540     *        A DOM element within which to query. Can also be a selector string in
4541     *        which case the first matching element will get used as context. If
4542     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4543     *        nothing it falls back to `document`.
4544     *
4545     * @return {Element|null}
4546     *         the dom element that was found, or null
4547     *
4548     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4549     */
4550    ;
4551
4552    _proto.$ = function $$1(selector, context) {
4553      return $(selector, context || this.contentEl());
4554    }
4555    /**
4556     * Finds all DOM element matching a `selector`. This can be within the `Component`s
4557     * `contentEl()` or another custom context.
4558     *
4559     * @param {string} selector
4560     *        A valid CSS selector, which will be passed to `querySelectorAll`.
4561     *
4562     * @param {Element|string} [context=this.contentEl()]
4563     *        A DOM element within which to query. Can also be a selector string in
4564     *        which case the first matching element will get used as context. If
4565     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4566     *        nothing it falls back to `document`.
4567     *
4568     * @return {NodeList}
4569     *         a list of dom elements that were found
4570     *
4571     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4572     */
4573    ;
4574
4575    _proto.$$ = function $$$1(selector, context) {
4576      return $$(selector, context || this.contentEl());
4577    }
4578    /**
4579     * Check if a component's element has a CSS class name.
4580     *
4581     * @param {string} classToCheck
4582     *        CSS class name to check.
4583     *
4584     * @return {boolean}
4585     *         - True if the `Component` has the class.
4586     *         - False if the `Component` does not have the class`
4587     */
4588    ;
4589
4590    _proto.hasClass = function hasClass$1(classToCheck) {
4591      return hasClass(this.el_, classToCheck);
4592    }
4593    /**
4594     * Add a CSS class name to the `Component`s element.
4595     *
4596     * @param {string} classToAdd
4597     *        CSS class name to add
4598     */
4599    ;
4600
4601    _proto.addClass = function addClass$1(classToAdd) {
4602      addClass(this.el_, classToAdd);
4603    }
4604    /**
4605     * Remove a CSS class name from the `Component`s element.
4606     *
4607     * @param {string} classToRemove
4608     *        CSS class name to remove
4609     */
4610    ;
4611
4612    _proto.removeClass = function removeClass$1(classToRemove) {
4613      removeClass(this.el_, classToRemove);
4614    }
4615    /**
4616     * Add or remove a CSS class name from the component's element.
4617     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4618     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4619     *
4620     * @param  {string} classToToggle
4621     *         The class to add or remove based on (@link Component#hasClass}
4622     *
4623     * @param  {boolean|Dom~predicate} [predicate]
4624     *         An {@link Dom~predicate} function or a boolean
4625     */
4626    ;
4627
4628    _proto.toggleClass = function toggleClass$1(classToToggle, predicate) {
4629      toggleClass(this.el_, classToToggle, predicate);
4630    }
4631    /**
4632     * Show the `Component`s element if it is hidden by removing the
4633     * 'vjs-hidden' class name from it.
4634     */
4635    ;
4636
4637    _proto.show = function show() {
4638      this.removeClass('vjs-hidden');
4639    }
4640    /**
4641     * Hide the `Component`s element if it is currently showing by adding the
4642     * 'vjs-hidden` class name to it.
4643     */
4644    ;
4645
4646    _proto.hide = function hide() {
4647      this.addClass('vjs-hidden');
4648    }
4649    /**
4650     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4651     * class name to it. Used during fadeIn/fadeOut.
4652     *
4653     * @private
4654     */
4655    ;
4656
4657    _proto.lockShowing = function lockShowing() {
4658      this.addClass('vjs-lock-showing');
4659    }
4660    /**
4661     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4662     * class name from it. Used during fadeIn/fadeOut.
4663     *
4664     * @private
4665     */
4666    ;
4667
4668    _proto.unlockShowing = function unlockShowing() {
4669      this.removeClass('vjs-lock-showing');
4670    }
4671    /**
4672     * Get the value of an attribute on the `Component`s element.
4673     *
4674     * @param {string} attribute
4675     *        Name of the attribute to get the value from.
4676     *
4677     * @return {string|null}
4678     *         - The value of the attribute that was asked for.
4679     *         - Can be an empty string on some browsers if the attribute does not exist
4680     *           or has no value
4681     *         - Most browsers will return null if the attibute does not exist or has
4682     *           no value.
4683     *
4684     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4685     */
4686    ;
4687
4688    _proto.getAttribute = function getAttribute$1(attribute) {
4689      return getAttribute(this.el_, attribute);
4690    }
4691    /**
4692     * Set the value of an attribute on the `Component`'s element
4693     *
4694     * @param {string} attribute
4695     *        Name of the attribute to set.
4696     *
4697     * @param {string} value
4698     *        Value to set the attribute to.
4699     *
4700     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4701     */
4702    ;
4703
4704    _proto.setAttribute = function setAttribute$1(attribute, value) {
4705      setAttribute(this.el_, attribute, value);
4706    }
4707    /**
4708     * Remove an attribute from the `Component`s element.
4709     *
4710     * @param {string} attribute
4711     *        Name of the attribute to remove.
4712     *
4713     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4714     */
4715    ;
4716
4717    _proto.removeAttribute = function removeAttribute$1(attribute) {
4718      removeAttribute(this.el_, attribute);
4719    }
4720    /**
4721     * Get or set the width of the component based upon the CSS styles.
4722     * See {@link Component#dimension} for more detailed information.
4723     *
4724     * @param {number|string} [num]
4725     *        The width that you want to set postfixed with '%', 'px' or nothing.
4726     *
4727     * @param {boolean} [skipListeners]
4728     *        Skip the componentresize event trigger
4729     *
4730     * @return {number|string}
4731     *         The width when getting, zero if there is no width. Can be a string
4732     *           postpixed with '%' or 'px'.
4733     */
4734    ;
4735
4736    _proto.width = function width(num, skipListeners) {
4737      return this.dimension('width', num, skipListeners);
4738    }
4739    /**
4740     * Get or set the height of the component based upon the CSS styles.
4741     * See {@link Component#dimension} for more detailed information.
4742     *
4743     * @param {number|string} [num]
4744     *        The height that you want to set postfixed with '%', 'px' or nothing.
4745     *
4746     * @param {boolean} [skipListeners]
4747     *        Skip the componentresize event trigger
4748     *
4749     * @return {number|string}
4750     *         The width when getting, zero if there is no width. Can be a string
4751     *         postpixed with '%' or 'px'.
4752     */
4753    ;
4754
4755    _proto.height = function height(num, skipListeners) {
4756      return this.dimension('height', num, skipListeners);
4757    }
4758    /**
4759     * Set both the width and height of the `Component` element at the same time.
4760     *
4761     * @param  {number|string} width
4762     *         Width to set the `Component`s element to.
4763     *
4764     * @param  {number|string} height
4765     *         Height to set the `Component`s element to.
4766     */
4767    ;
4768
4769    _proto.dimensions = function dimensions(width, height) {
4770      // Skip componentresize listeners on width for optimization
4771      this.width(width, true);
4772      this.height(height);
4773    }
4774    /**
4775     * Get or set width or height of the `Component` element. This is the shared code
4776     * for the {@link Component#width} and {@link Component#height}.
4777     *
4778     * Things to know:
4779     * - If the width or height in an number this will return the number postfixed with 'px'.
4780     * - If the width/height is a percent this will return the percent postfixed with '%'
4781     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4782     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4783     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4783sing-width/}
4784     *   for more information
4785     * - If you want the computed style of the component, use {@link Component#currentWidth}
4786     *   and {@link {Component#currentHeight}
4787     *
4788     * @fires Component#componentresize
4789     *
4790     * @param {string} widthOrHeight
4791     8        'width' or 'height'
4792     *
4793     * @param  {number|string} [num]
4794     8         New dimension
4795     *
4796     * @param  {boolean} [skipListeners]
4797     *         Skip componentresize event trigger
4798     *
4799     * @return {number}
4800     *         The dimension when getting or 0 if unset
4801     */
4802    ;
4803
4804    _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
4805      if (num !== undefined) {
4806        // Set to zero if null or literally NaN (NaN !== NaN)
4807        if (num === null || num !== num) {
4808          num = 0;
4809        } // Check if using css width/height (% or px) and adjust
4810
4811
4812        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4813          this.el_.style[widthOrHeight] = num;
4814        } else if (num === 'auto') {
4815          this.el_.style[widthOrHeight] = '';
4816        } else {
4817          this.el_.style[widthOrHeight] = num + 'px';
4818        } // skipListeners allows us to avoid triggering the resize event when setting both width and height
4819
4820
4821        if (!skipListeners) {
4822          /**
4823           * Triggered when a component is resized.
4824           *
4825           * @event Component#componentresize
4826           * @type {EventTarget~Event}
4827           */
4828          this.trigger('componentresize');
4829        }
4830
4831        return;
4832      } // Not setting a value, so getting it
4833      // Make sure element exists
4834
4835
4836      if (!this.el_) {
4837        return 0;
4838      } // Get dimension value from style
4839
4840
4841      var val = this.el_.style[widthOrHeight];
4842      var pxIndex = val.indexOf('px');
4843
4844      if (pxIndex !== -1) {
4845        // Return the pixel value with no 'px'
4846        return parseInt(val.slice(0, pxIndex), 10);
4847      } // No px so using % or no style was set, so falling back to offsetWidth/height
4848      // If component has display:none, offset will return 0
4849      // TODO: handle display:none and no dimension style using px
4850
4851
4852      return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
4853    }
4854    /**
4855     * Get the computed width or the height of the component's element.
4856     *
4857     * Uses `window.getComputedStyle`.
4858     *
4859     * @param {string} widthOrHeight
4860     *        A string containing 'width' or 'height'. Whichever one you want to get.
4861     *
4862     * @return {number}
4863     *         The dimension that gets asked for or 0 if nothing was set
4864     *         for that dimension.
4865     */
4866    ;
4867
4868    _proto.currentDimension = function currentDimension(widthOrHeight) {
4869      var computedWidthOrHeight = 0;
4870
4871      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4872        throw new Error('currentDimension only accepts width or height value');
4873      }
4874
4875      computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer
4876
4877      computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
4878      // and we want to check the offset values.
4879      // This code also runs wherever getComputedStyle doesn't exist.
4880
4881      if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4882        var rule = "offset" + toTitleCase$1(widthOrHeight);
4883        computedWidthOrHeight = this.el_[rule];
4884      }
4885
4886      return computedWidthOrHeight;
4887    }
4888    /**
4889     * An object that contains width and height values of the `Component`s
4890     * computed style. Uses `window.getComputedStyle`.
4891     *
4892     * @typedef {Object} Component~DimensionObject
4893     *
4894     * @property {number} width
4895     *           The width of the `Component`s computed style.
4896     *
4897     * @property {number} height
4898     *           The height of the `Component`s computed style.
4899     */
4900
4901    /**
4902     * Get an object that contains computed width and height values of the
4903     * component's element.
4904     *
4905     * Uses `window.getComputedStyle`.
4906     *
4907     * @return {Component~DimensionObject}
4908     *         The computed dimensions of the component's element.
4909     */
4910    ;
4911
4912    _proto.currentDimensions = function currentDimensions() {
4913      return {
4914        width: this.currentDimension('width'),
4915        height: this.currentDimension('height')
4916      };
4917    }
4918    /**
4919     * Get the computed width of the component's element.
4920     *
4921     * Uses `window.getComputedStyle`.
4922     *
4923     * @return {number}
4924     *         The computed width of the component's element.
4925     */
4926    ;
4927
4928    _proto.currentWidth = function currentWidth() {
4929      return this.currentDimension('width');
4930    }
4931    /**
4932     * Get the computed height of the component's element.
4933     *
4934     * Uses `window.getComputedStyle`.
4935     *
4936     * @return {number}
4937     *         The computed height of the component's element.
4938     */
4939    ;
4940
4941    _proto.currentHeight = function currentHeight() {
4942      return this.currentDimension('height');
4943    }
4944    /**
4945     * Set the focus to this component
4946     */
4947    ;
4948
4949    _proto.focus = function focus() {
4950      this.el_.focus();
4951    }
4952    /**
4953     * Remove the focus from this component
4954     */
4955    ;
4956
4957    _proto.blur = function blur() {
4958      this.el_.blur();
4959    }
4960    /**
4961     * When this Component receives a `keydown` event which it does not process,
4962     *  it passes the event to the Player for handling.
4963     *
4964     * @param {EventTarget~Event} event
4965     *        The `keydown` event that caused this function to be called.
4966     */
4967    ;
4968
4969    _proto.handleKeyDown = function handleKeyDown(event) {
4970      if (this.player_) {
4971        // We only stop propagation here because we want unhandled events to fall
4972        // back to the browser.
4973        event.stopPropagation();
4974        this.player_.handleKeyDown(event);
4975      }
4976    }
4977    /**
4978     * Many components used to have a `handleKeyPress` method, which was poorly
4979     * named because it listened to a `keydown` event. This method name now
4980     * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4981     * will not see their method calls stop working.
4982     *
4983     * @param {EventTarget~Event} event
4984     *        The event that caused this function to be called.
4985     */
4986    ;
4987
4988    _proto.handleKeyPress = function handleKeyPress(event) {
4989      this.handleKeyDown(event);
4990    }
4991    /**
4992     * Emit a 'tap' events when touch event support gets detected. This gets used to
4993     * support toggling the controls through a tap on the video. They get enabled
4994     * because every sub-component would have extra overhead otherwise.
4995     *
4996     * @private
4997     * @fires Component#tap
4998     * @listens Component#touchstart
4999     * @listens Component#touchmove
5000     * @listens Component#touchleave
5001     * @listens Component#touchcancel
5002     * @listens Component#touchend
5003      */
5004    ;
5005
5006    _proto.emitTapEvents = function emitTapEvents() {
5007      // Track the start time so we can determine how long the touch lasted
5008      var touchStart = 0;
5009      var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
5010      // Other popular libs use anywhere from 2 (hammer.js) to 15,
5011      // so 10 seems like a nice, round number.
5012
5013      var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
5014
5015      var touchTimeThreshold = 200;
5016      var couldBeTap;
5017      this.on('touchstart', function (event) {
5018        // If more than one finger, don't consider treating this as a click
5019        if (event.touches.length === 1) {
5020          // Copy pageX/pageY from the object
5021          firstTouch = {
5022            pageX: event.touches[0].pageX,
5023            pageY: event.touches[0].pageY
5024          }; // Record start time so we can detect a tap vs. "touch and hold"
5025
5026          touchStart = window_1.performance.now(); // Reset couldBeTap tracking
5027
5028          couldBeTap = true;
5029        }
5030      });
5031      this.on('touchmove', function (event) {
5032        // If more than one finger, don't consider treating this as a click
5033        if (event.touches.length > 1) {
5034          couldBeTap = false;
5035        } else if (firstTouch) {
5036          // Some devices will throw touchmoves for all but the slightest of taps.
5037          // So, if we moved only a small distance, this could still be a tap
5038          var xdiff = event.touches[0].pageX - firstTouch.pageX;
5039          var ydiff = event.touches[0].pageY - firstTouch.pageY;
5040          var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
5041
5042          if (touchDistance > tapMovementThreshold) {
5043            couldBeTap = false;
5044          }
5045        }
5046      });
5047
5048      var noTap = function noTap() {
5049        couldBeTap = false;
5050      }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
5051
5052
5053      this.on('touchleave', noTap);
5054      this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
5055      // event
5056
5057      this.on('touchend', function (event) {
5058        firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
5059
5060        if (couldBeTap === true) {
5061          // Measure how long the touch lasted
5062          var touchTime = window_1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
5063
5064          if (touchTime < touchTimeThreshold) {
5065            // Don't let browser turn this into a click
5066            event.preventDefault();
5067            /**
5068             * Triggered when a `Component` is tapped.
5069             *
5070             * @event Component#tap
5071             * @type {EventTarget~Event}
5072             */
5073
5074            this.trigger('tap'); // It may be good to copy the touchend event object and change the
5075            // type to tap, if the other event properties aren't exact after
5076            // Events.fixEvent runs (e.g. event.target)
5077          }
5078        }
5079      });
5080    }
5081    /**
5082     * This function reports user activity whenever touch events happen. This can get
5083     * turned off by any sub-components that wants touch events to act another way.
5084     *
5085     * Report user touch activity when touch events occur. User activity gets used to
5086     * determine when controls should show/hide. It is simple when it comes to mouse
5087     * events, because any mouse event should show the controls. So we capture mouse
5088     * events that bubble up to the player and report activity when that happens.
5089     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
5090     * controls. So touch events can't help us at the player level either.
5091     *
5092     * User activity gets checked asynchronously. So what could happen is a tap event
5093     * on the video turns the controls off. Then the `touchend` event bubbles up to
5094     * the player. Which, if it reported user activity, would turn the controls right
5095     * back on. We also don't want to completely block touch events from bubbling up.
5096     * Furthermore a `touchmove` event and anything other than a tap, should not turn
5097     * controls back on.
5098     *
5099     * @listens Component#touchstart
5100     * @listens Component#touchmove
5101     * @listens Component#touchend
5102     * @listens Component#touchcancel
5103     */
5104    ;
5105
5106    _proto.enableTouchActivity = function enableTouchActivity() {
5107      // Don't continue if the root player doesn't support reporting user activity
5108      if (!this.player() || !this.player().reportUserActivity) {
5109        return;
5110      } // listener for reporting that the user is active
5111
5112
5113      var report = bind(this.player(), this.player().reportUserActivity);
5114      var touchHolding;
5115      this.on('touchstart', function () {
5116        report(); // For as long as the they are touching the device or have their mouse down,
5117        // we consider them active even if they're not moving their finger or mouse.
5118        // So we want to continue to update that they are active
5119
5120        this.clearInterval(touchHolding); // report at the same interval as activityCheck
5121
5122        touchHolding = this.setInterval(report, 250);
5123      });
5124
5125      var touchEnd = function touchEnd(event) {
5126        report(); // stop the interval that maintains activity if the touch is holding
5127
5128        this.clearInterval(touchHolding);
5129      };
5130
5131      this.on('touchmove', report);
5132      this.on('touchend', touchEnd);
5133      this.on('touchcancel', touchEnd);
5134    }
5135    /**
5136     * A callback that has no parameters and is bound into `Component`s context.
5137     *
5138     * @callback Component~GenericCallback
5139     * @this Component
5140     */
5141
5142    /**
5143     * Creates a function that runs after an `x` millisecond timeout. This function is a
5144     * wrapper around `window.setTimeout`. There are a few reasons to use this one
5145     * instead though:
5146     * 1. It gets cleared via  {@link Component#clearTimeout} when
5147     *    {@link Component#dispose} gets called.
5148     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
5149     *
5150     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
5151     *         will cause its dispose listener not to get cleaned up! Please use
5152     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
5153     *
5154     * @param {Component~GenericCallback} fn
5155     *        The function that will be run after `timeout`.
5156     *
5157     * @param {number} timeout
5158     *        Timeout in milliseconds to delay before executing the specified function.
5159     *
5160     * @return {number}
5161     *         Returns a timeout ID that gets used to identify the timeout. It can also
5162     *         get used in {@link Component#clearTimeout} to clear the timeout that
5163     *         was set.
5164     *
5165     * @listens Component#dispose
5166     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
5167     */
5168    ;
5169
5170    _proto.setTimeout = function setTimeout(fn, timeout) {
5171      var _this2 = this;
5172
5173      // declare as variables so they are properly available in timeout function
5174      // eslint-disable-next-line
5175      var timeoutId;
5176      fn = bind(this, fn);
5177      this.clearTimersOnDispose_();
5178      timeoutId = window_1.setTimeout(function () {
5179        if (_this2.setTimeoutIds_.has(timeoutId)) {
5180          _this2.setTimeoutIds_["delete"](timeoutId);
5181        }
5182
5183        fn();
5184      }, timeout);
5185      this.setTimeoutIds_.add(timeoutId);
5186      return timeoutId;
5187    }
5188    /**
5189     * Clears a timeout that gets created via `window.setTimeout` or
5190     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
5191     * use this function instead of `window.clearTimout`. If you don't your dispose
5192     * listener will not get cleaned up until {@link Component#dispose}!
5193     *
5194     * @param {number} timeoutId
5195     *        The id of the timeout to clear. The return value of
5196     *        {@link Component#setTimeout} or `window.setTimeout`.
5197     *
5198     * @return {number}
5199     *         Returns the timeout id that was cleared.
5200     *
5201     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
5202     */
5203    ;
5204
5205    _proto.clearTimeout = function clearTimeout(timeoutId) {
5206      if (this.setTimeoutIds_.has(timeoutId)) {
5207        this.setTimeoutIds_["delete"](timeoutId);
5208        window_1.clearTimeout(timeoutId);
5209      }
5210
5211      return timeoutId;
5212    }
5213    /**
5214     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
5215     * around `window.setInterval`. There are a few reasons to use this one instead though.
5216     * 1. It gets cleared via  {@link Component#clearInterval} when
5217     *    {@link Component#dispose} gets called.
5218     * 2. The function callback will be a {@link Component~GenericCallback}
5219     *
5220     * @param {Component~GenericCallback} fn
5221     *        The function to run every `x` seconds.
5222     *
5223     * @param {number} interval
5224     *        Execute the specified function every `x` milliseconds.
5225     *
5226     * @return {number}
5227     *         Returns an id that can be used to identify the interval. It can also be be used in
5228     *         {@link Component#clearInterval} to clear the interval.
5229     *
5230     * @listens Component#dispose
5231     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
5232     */
5233    ;
5234
5235    _proto.setInterval = function setInterval(fn, interval) {
5236      fn = bind(this, fn);
5237      this.clearTimersOnDispose_();
5238      var intervalId = window_1.setInterval(fn, interval);
5239      this.setIntervalIds_.add(intervalId);
5240      return intervalId;
5241    }
5242    /**
5243     * Clears an interval that gets created via `window.setInterval` or
5244     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
5245     * use this function instead of `window.clearInterval`. If you don't your dispose
5246     * listener will not get cleaned up until {@link Component#dispose}!
5247     *
5248     * @param {number} intervalId
5249     *        The id of the interval to clear. The return value of
5250     *        {@link Component#setInterval} or `window.setInterval`.
5251     *
5252     * @return {number}
5253     *         Returns the interval id that was cleared.
5254     *
5255     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
5256     */
5257    ;
5258
5259    _proto.clearInterval = function clearInterval(intervalId) {
5260      if (this.setIntervalIds_.has(intervalId)) {
5261        this.setIntervalIds_["delete"](intervalId);
5262        window_1.clearInterval(intervalId);
5263      }
5264
5265      return intervalId;
5266    }
5267    /**
5268     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
5269     * with a few extra bonuses:
5270     *
5271     * - Supports browsers that do not support rAF by falling back to
5272     *   {@link Component#setTimeout}.
5273     *
5274     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
5275     *   bound to the component).
5276     *
5277     * - Automatic cancellation of the rAF callback is handled if the component
5278     *   is disposed before it is called.
5279     *
5280     * @param  {Component~GenericCallback} fn
5281     *         A function that will be bound to this component and executed just
5282     *         before the browser's next repaint.
5283     *
5284     * @return {number}
5285     *         Returns an rAF ID that gets used to identify the timeout. It can
5286     *         also be used in {@link Component#cancelAnimationFrame} to cancel
5287     *         the animation frame callback.
5288     *
5289     * @listens Component#dispose
5290     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
5291     */
5292    ;
5293
5294    _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
5295      var _this3 = this;
5296
5297      // Fall back to using a timer.
5298      if (!this.supportsRaf_) {
5299        return this.setTimeout(fn, 1000 / 60);
5300      }
5301
5302      this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function
5303      // eslint-disable-next-line
5304
5305      var id;
5306      fn = bind(this, fn);
5307      id = window_1.requestAnimationFrame(function () {
5308        if (_this3.rafIds_.has(id)) {
5309          _this3.rafIds_["delete"](id);
5310        }
5311
5312        fn();
5313      });
5314      this.rafIds_.add(id);
5315      return id;
5316    }
5317    /**
5318     * Request an animation frame, but only one named animation
5319     * frame will be queued. Another will never be added until
5320     * the previous one finishes.
5321     *
5322     * @param {string} name
5323     *        The name to give this requestAnimationFrame
5324     *
5325     * @param  {Component~GenericCallback} fn
5326     *         A function that will be bound to this component and executed just
5327     *         before the browser's next repaint.
5328     */
5329    ;
5330
5331    _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) {
5332      var _this4 = this;
5333
5334      if (this.namedRafs_.has(name)) {
5335        return;
5336      }
5337
5338      this.clearTimersOnDispose_();
5339      fn = bind(this, fn);
5340      var id = this.requestAnimationFrame(function () {
5341        fn();
5342
5343        if (_this4.namedRafs_.has(name)) {
5344          _this4.namedRafs_["delete"](name);
5345        }
5346      });
5347      this.namedRafs_.set(name, id);
5348      return name;
5349    }
5350    /**
5351     * Cancels a current named animation frame if it exists.
5352     *
5353     * @param {string} name
5354     *        The name of the requestAnimationFrame to cancel.
5355     */
5356    ;
5357
5358    _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) {
5359      if (!this.namedRafs_.has(name)) {
5360        return;
5361      }
5362
5363      this.cancelAnimationFrame(this.namedRafs_.get(name));
5364      this.namedRafs_["delete"](name);
5365    }
5366    /**
5367     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
5368     * (rAF).
5369     *
5370     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
5371     * use this function instead of `window.cancelAnimationFrame`. If you don't,
5372     * your dispose listener will not get cleaned up until {@link Component#dispose}!
5373     *
5374     * @param {number} id
5375     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
5376     *
5377     * @return {number}
5378     *         Returns the rAF ID that was cleared.
5379     *
5380     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
5381     */
5382    ;
5383
5384    _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
5385      // Fall back to using a timer.
5386      if (!this.supportsRaf_) {
5387        return this.clearTimeout(id);
5388      }
5389
5390      if (this.rafIds_.has(id)) {
5391        this.rafIds_["delete"](id);
5392        window_1.cancelAnimationFrame(id);
5393      }
5394
5395      return id;
5396    }
5397    /**
5398     * A function to setup `requestAnimationFrame`, `setTimeout`,
5399     * and `setInterval`, clearing on dispose.
5400     *
5401     * > Previously each timer added and removed dispose listeners on it's own.
5402     * For better performance it was decided to batch them all, and use `Set`s
5403     * to track outstanding timer ids.
5404     *
5405     * @private
5406     */
5407    ;
5408
5409    _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() {
5410      var _this5 = this;
5411
5412      if (this.clearingTimersOnDispose_) {
5413        return;
5414      }
5415
5416      this.clearingTimersOnDispose_ = true;
5417      this.one('dispose', function () {
5418        [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
5418h(function (_ref) {
5419          var idName = _ref[0],
5420              cancelName = _ref[1];
5421
5422          // for a `Set` key will actually be the value again
5423          // so forEach((val, val) =>` but for maps we want to use
5424          // the key.
5425          _this5[idName].forEach(function (val, key) {
5426            return _this5[cancelName](key);
5427          });
5428        });
5429        _this5.clearingTimersOnDispose_ = false;
5430      });
5431    }
5432    /**
5433     * Register a `Component` with `videojs` given the name and the component.
5434     *
5435     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
5436     *         should be registered using {@link Tech.registerTech} or
5437     *         {@link videojs:videojs.registerTech}.
5438     *
5439     * > NOTE: This function can also be seen on videojs as
5440     *         {@link videojs:videojs.registerComponent}.
5441     *
5442     * @param {string} name
5443     *        The name of the `Component` to register.
5444     *
5445     * @param {Component} ComponentToRegister
5446     *        The `Component` class to register.
5447     *
5448     * @return {Component}
5449     *         The `Component` that was registered.
5450     */
5451    ;
5452
5453    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
5454      if (typeof name !== 'string' || !name) {
5455        throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
5456      }
5457
5458      var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
5459
5460      var isTech = Tech && Tech.isTech(ComponentToRegister);
5461      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
5462
5463      if (isTech || !isComp) {
5464        var reason;
5465
5466        if (isTech) {
5467          reason = 'techs must be registered using Tech.registerTech()';
5468        } else {
5469          reason = 'must be a Component subclass';
5470        }
5471
5472        throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
5473      }
5474
5475      name = toTitleCase$1(name);
5476
5477      if (!Component.components_) {
5478        Component.components_ = {};
5479      }
5480
5481      var Player = Component.getComponent('Player');
5482
5483      if (name === 'Player' && Player && Player.players) {
5484        var players = Player.players;
5485        var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
5486        // in Players.players. So, we must loop through and verify that the value
5487        // for each item is not null. This allows registration of the Player component
5488        // after all players have been disposed or before any were created.
5489
5490        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
5491          return players[pname];
5492        }).every(Boolean)) {
5493          throw new Error('Can not register Player component after player has been created.');
5494        }
5495      }
5496
5497      Component.components_[name] = ComponentToRegister;
5498      Component.components_[toLowerCase(name)] = ComponentToRegister;
5499      return ComponentToRegister;
5500    }
5501    /**
5502     * Get a `Component` based on the name it was registered with.
5503     *
5504     * @param {string} name
5505     *        The Name of the component to get.
5506     *
5507     * @return {Component}
5508     *         The `Component` that got registered under the given name.
5509     *
5510     * @deprecated In `videojs` 6 this will not return `Component`s that were not
5511     *             registered using {@link Component.registerComponent}. Currently we
5512     *             check the global `videojs` object for a `Component` name and
5513     *             return that if it exists.
5514     */
5515    ;
5516
5517    Component.getComponent = function getComponent(name) {
5518      if (!name || !Component.components_) {
5519        return;
5520      }
5521
5522      return Component.components_[name];
5523    };
5524
5525    return Component;
5526  }();
5527  /**
5528   * Whether or not this component supports `requestAnimationFrame`.
5529   *
5530   * This is exposed primarily for testing purposes.
5531   *
5532   * @private
5533   * @type {Boolean}
5534   */
5535
5536
5537  Component$1.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function';
5538  Component$1.registerComponent('Component', Component$1);
5539
5540  function _assertThisInitialized(self) {
5541    if (self === void 0) {
5542      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
5543    }
5544
5545    return self;
5546  }
5547
5548  var assertThisInitialized = _assertThisInitialized;
5549
5550  function _inheritsLoose(subClass, superClass) {
5551    subClass.prototype = Object.create(superClass.prototype);
5552    subClass.prototype.constructor = subClass;
5553    subClass.__proto__ = superClass;
5554  }
5555
5556  var inheritsLoose = _inheritsLoose;
5557
5558  /**
5559   * @file time-ranges.js
5560   * @module time-ranges
5561   */
5562  /**
5563   * Returns the time for the specified index at the start or end
5564   * of a TimeRange object.
5565   *
5566   * @typedef    {Function} TimeRangeIndex
5567   *
5568   * @param      {number} [index=0]
5569   *             The range number to return the time for.
5570   *
5571   * @return     {number}
5572   *             The time offset at the specified index.
5573   *
5574   * @deprecated The index argument must be provided.
5575   *             In the future, leaving it out will throw an error.
5576   */
5577
5578  /**
5579   * An object that contains ranges of time.
5580   *
5581   * @typedef  {Object} TimeRange
5582   *
5583   * @property {number} length
5584   *           The number of time ranges represented by this object.
5585   *
5586   * @property {module:time-ranges~TimeRangeIndex} start
5587   *           Returns the time offset at which a specified time range begins.
5588   *
5589   * @property {module:time-ranges~TimeRangeIndex} end
5590   *           Returns the time offset at which a specified time range ends.
5591   *
5592   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5593   */
5594
5595  /**
5596   * Check if any of the time ranges are over the maximum index.
5597   *
5598   * @private
5599   * @param   {string} fnName
5600   *          The function name to use for logging
5601   *
5602   * @param   {number} index
5603   *          The index to check
5604   *
5605   * @param   {number} maxIndex
5606   *          The maximum possible index
5607   *
5608   * @throws  {Error} if the timeRanges provided are over the maxIndex
5609   */
5610
5611  function rangeCheck(fnName, index, maxIndex) {
5612    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5613      throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
5614    }
5615  }
5616  /**
5617   * Get the time for the specified index at the start or end
5618   * of a TimeRange object.
5619   *
5620   * @private
5621   * @param      {string} fnName
5622   *             The function name to use for logging
5623   *
5624   * @param      {string} valueIndex
5625   *             The property that should be used to get the time. should be
5626   *             'start' or 'end'
5627   *
5628   * @param      {Array} ranges
5629   *             An array of time ranges
5630   *
5631   * @param      {Array} [rangeIndex=0]
5632   *             The index to start the search at
5633   *
5634   * @return     {number}
5635   *             The time that offset at the specified index.
5636   *
5637   * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5638   * @throws     {Error} if rangeIndex is more than the length of ranges
5639   */
5640
5641
5642  function getRange(fnName, valueIndex, ranges, rangeIndex) {
5643    rangeCheck(fnName, rangeIndex, ranges.length - 1);
5644    return ranges[rangeIndex][valueIndex];
5645  }
5646  /**
5647   * Create a time range object given ranges of time.
5648   *
5649   * @private
5650   * @param   {Array} [ranges]
5651   *          An array of time ranges.
5652   */
5653
5654
5655  function createTimeRangesObj(ranges) {
5656    var timeRangesObj;
5657
5658    if (ranges === undefined || ranges.length === 0) {
5659      timeRangesObj = {
5660        length: 0,
5661        start: function start() {
5662          throw new Error('This TimeRanges object is empty');
5663        },
5664        end: function end() {
5665          throw new Error('This TimeRanges object is empty');
5666        }
5667      };
5668    } else {
5669      timeRangesObj = {
5670        length: ranges.length,
5671        start: getRange.bind(null, 'start', 0, ranges),
5672        end: getRange.bind(null, 'end', 1, ranges)
5673      };
5674    }
5675
5676    if (window_1.Symbol && window_1.Symbol.iterator) {
5677      timeRangesObj[window_1.Symbol.iterator] = function () {
5678        return (ranges || []).values();
5679      };
5680    }
5681
5682    return timeRangesObj;
5683  }
5684  /**
5685   * Create a `TimeRange` object which mimics an
5686   * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5687   *
5688   * @param {number|Array[]} start
5689   *        The start of a single range (a number) or an array of ranges (an
5690   *        array of arrays of two numbers each).
5691   *
5692   * @param {number} end
5693   *        The end of a single range. Cannot be used with the array form of
5694   *        the `start` argument.
5695   */
5696
5697
5698  function createTimeRanges(start, end) {
5699    if (Array.isArray(start)) {
5700      return createTimeRangesObj(start);
5701    } else if (start === undefined || end === undefined) {
5702      return createTimeRangesObj();
5703    }
5704
5705    return createTimeRangesObj([[start, end]]);
5706  }
5707
5708  /**
5709   * @file buffer.js
5710   * @module buffer
5711   */
5712  /**
5713   * Compute the percentage of the media that has been buffered.
5714   *
5715   * @param {TimeRange} buffered
5716   *        The current `TimeRange` object representing buffered time ranges
5717   *
5718   * @param {number} duration
5719   *        Total duration of the media
5720   *
5721   * @return {number}
5722   *         Percent buffered of the total duration in decimal form.
5723   */
5724
5725  function bufferedPercent(buffered, duration) {
5726    var bufferedDuration = 0;
5727    var start;
5728    var end;
5729
5730    if (!duration) {
5731      return 0;
5732    }
5733
5734    if (!buffered || !buffered.length) {
5735      buffered = createTimeRanges(0, 0);
5736    }
5737
5738    for (var i = 0; i < buffered.length; i++) {
5739      start = buffered.start(i);
5740      end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
5741
5742      if (end > duration) {
5743        end = duration;
5744      }
5745
5746      bufferedDuration += end - start;
5747    }
5748
5749    return bufferedDuration / duration;
5750  }
5751
5752  /**
5753   * @file media-error.js
5754   */
5755  /**
5756   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5757   *
5758   * @param {number|string|Object|MediaError} value
5759   *        This can be of multiple types:
5760   *        - number: should be a standard error code
5761   *        - string: an error message (the code will be 0)
5762   *        - Object: arbitrary properties
5763   *        - `MediaError` (native): used to populate a video.js `MediaError` object
5764   *        - `MediaError` (video.js): will return itself if it's already a
5765   *          video.js `MediaError` object.
5766   *
5767   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5768   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5769   *
5770   * @class MediaError
5771   */
5772
5773  function MediaError(value) {
5774    // Allow redundant calls to this constructor to avoid having `instanceof`
5775    // checks peppered around the code.
5776    if (value instanceof MediaError) {
5777      return value;
5778    }
5779
5780    if (typeof value === 'number') {
5781      this.code = value;
5782    } else if (typeof value === 'string') {
5783      // default code is zero, so this is a custom error
5784      this.message = value;
5785    } else if (isObject$1(value)) {
5786      // We assign the `code` property manually because native `MediaError` objects
5787      // do not expose it as an own/enumerable property of the object.
5788      if (typeof value.code === 'number') {
5789        this.code = value.code;
5790      }
5791
5792      assign(this, value);
5793    }
5794
5795    if (!this.message) {
5796      this.message = MediaError.defaultMessages[this.code] || '';
5797    }
5798  }
5799  /**
5800   * The error code that refers two one of the defined `MediaError` types
5801   *
5802   * @type {Number}
5803   */
5804
5805
5806  MediaError.prototype.code = 0;
5807  /**
5808   * An optional message that to show with the error. Message is not part of the HTML5
5809   * video spec but allows for more informative custom errors.
5810   *
5811   * @type {String}
5812   */
5813
5814  MediaError.prototype.message = '';
5815  /**
5816   * An optional status code that can be set by plugins to allow even more detail about
5817   * the error. For example a plugin might provide a specific HTTP status code and an
5818   * error message for that code. Then when the plugin gets that error this class will
5819   * know how to display an error message for it. This allows a custom message to show
5820   * up on the `Player` error overlay.
5821   *
5822   * @type {Array}
5823   */
5824
5825  MediaError.prototype.status = null;
5826  /**
5827   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5828   * specification listed under {@link MediaError} for more information.
5829   *
5830   * @enum {array}
5831   * @readonly
5832   * @property {string} 0 - MEDIA_ERR_CUSTOM
5833   * @property {string} 1 - MEDIA_ERR_ABORTED
5834   * @property {string} 2 - MEDIA_ERR_NETWORK
5835   * @property {string} 3 - MEDIA_ERR_DECODE
5836   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5837   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5838   */
5839
5840  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5841  /**
5842   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5843   *
5844   * @type {Array}
5845   * @constant
5846   */
5847
5848  MediaError.defaultMessages = {
5849    1: 'You aborted the media playback',
5850    2: 'A network error caused the media download to fail part-way.',
5851    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5852    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5853    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5854  }; // Add types as properties on MediaError
5855  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5856
5857  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5858    MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
5859
5860    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5861  } // jsdocs for instance/static members added above
5862
5863  var tuple = SafeParseTuple;
5864
5865  function SafeParseTuple(obj, reviver) {
5866    var json;
5867    var error = null;
5868
5869    try {
5870      json = JSON.parse(obj, reviver);
5871    } catch (err) {
5872      error = err;
5873    }
5874
5875    return [error, json];
5876  }
5877
5878  /**
5879   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5880   *
5881   * @param  {Object}  value
5882   *         An object that may or may not be `Promise`-like.
5883   *
5884   * @return {boolean}
5885   *         Whether or not the object is `Promise`-like.
5886   */
5887  function isPromise(value) {
5888    return value !== undefined && value !== null && typeof value.then === 'function';
5889  }
5890  /**
5891   * Silence a Promise-like object.
5892   *
5893   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5894   * play promise" rejection error messages.
5895   *
5896   * @param  {Object} value
5897   *         An object that may or may not be `Promise`-like.
5898   */
5899
5900  function silencePromise(value) {
5901    if (isPromise(value)) {
5902      value.then(null, function (e) {});
5903    }
5904  }
5905
5906  /**
5907   * @file text-track-list-converter.js Utilities for capturing text track state and
5908   * re-creating tracks based on a capture.
5909   *
5910   * @module text-track-list-converter
5911   */
5912
5913  /**
5914   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5915   * represents the {@link TextTrack}'s state.
5916   *
5917   * @param {TextTrack} track
5918   *        The text track to query.
5919   *
5920   * @return {Object}
5921   *         A serializable javascript representation of the TextTrack.
5922   * @private
5923   */
5924  var trackToJson_ = function trackToJson_(track) {
5925    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5926      if (track[prop]) {
5927        acc[prop] = track[prop];
5928      }
5929
5930      return acc;
5931    }, {
5932      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5933        return {
5934          startTime: cue.startTime,
5935          endTime: cue.endTime,
5936          text: cue.text,
5937          id: cue.id
5938        };
5939      })
5940    });
5941    return ret;
5942  };
5943  /**
5944   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5945   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5946   * {@link text-track-list-converter:jsonToTextTracks}.
5947   *
5948   * @param {Tech} tech
5949   *        The tech object to query
5950   *
5951   * @return {Array}
5952   *         A serializable javascript representation of the {@link Tech}s
5953   *         {@link TextTrackList}.
5954   */
5955
5956
5957  var textTracksToJson = function textTracksToJson(tech) {
5958    var trackEls = tech.$$('track');
5959    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5960      return t.track;
5961    });
5962    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5963      var json = trackToJson_(trackEl.track);
5964
5965      if (trackEl.src) {
5966        json.src = trackEl.src;
5967      }
5968
5969      return json;
5970    });
5971    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5972      return trackObjs.indexOf(track) === -1;
5973    }).map(trackToJson_));
5974  };
5975  /**
5976   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5977   * object {@link TextTrack} representations.
5978   *
5979   * @param {Array} json
5980   *        An array of `TextTrack` representation objects, like those that would be
5981   *        produced by `textTracksToJson`.
5982   *
5983   * @param {Tech} tech
5984   *        The `Tech` to create the `TextTrack`s on.
5985   */
5986
5987
5988  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5989    json.forEach(function (track) {
5990      var addedTrack = tech.addRemoteTextTrack(track, true).track;
5991
5992      if (!track.src && track.cues) {
5993        track.cues.forEach(function (cue) {
5994          return addedTrack.addCue(cue);
5995        });
5996      }
5997    });
5998    return tech.textTracks();
5999  };
6000
6001  var textTrackConverter = {
6002    textTracksToJson: textTracksToJson,
6003    jsonToTextTracks: jsonToTextTracks,
6004    trackToJson_: trackToJson_
6005  };
6006
6007  var keycode = createCommonjsModule(function (module, exports) {
6008    // Source: http://jsfiddle.net/vWx8V/
6009    // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
6010
6011    /**
6012     * Conenience method returns corresponding value for given keyName or keyCode.
6013     *
6014     * @param {Mixed} keyCode {Number} or keyName {String}
6015     * @return {Mixed}
6016     * @api public
6017     */
6018    function keyCode(searchInput) {
6019      // Keyboard Events
6020      if (searchInput && 'object' === typeof searchInput) {
6021        var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
6022        if (hasKeyCode) searchInput = hasKeyCode;
6023      } // Numbers
6024
6025
6026      if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
6027
6028      var search = String(searchInput); // check codes
6029
6030      var foundNamedKey = codes[search.toLowerCase()];
6031      if (foundNamedKey) return foundNamedKey; // check aliases
6032
6033      var foundNamedKey = aliases[search.toLowerCase()];
6034      if (foundNamedKey) return foundNamedKey; // weird character?
6035
6036      if (search.length === 1) return search.charCodeAt(0);
6037      return undefined;
6038    }
6039    /**
6040     * Compares a keyboard event with a given keyCode or keyName.
6041     *
6042     * @param {Event} event Keyboard event that should be tested
6043     * @param {Mixed} keyCode {Number} or keyName {String}
6044     * @return {Boolean}
6045     * @api public
6046     */
6047
6048
6049    keyCode.isEventKey = function isEventKey(event, nameOrCode) {
6050      if (event && 'object' === typeof event) {
6051        var keyCode = event.which || event.keyCode || event.charCode;
6052
6053        if (keyCode === null || keyCode === undefined) {
6054          return false;
6055        }
6056
6057        if (typeof nameOrCode === 'string') {
6058          // check codes
6059          var foundNamedKey = codes[nameOrCode.toLowerCase()];
6060
6061          if (foundNamedKey) {
6062            return foundNamedKey === keyCode;
6063          } // check aliases
6064
6065
6066          var foundNamedKey = aliases[nameOrCode.toLowerCase()];
6067
6068          if (foundNamedKey) {
6069            return foundNamedKey === keyCode;
6070          }
6071        } else if (typeof nameOrCode === 'number') {
6072          return nameOrCode === keyCode;
6073        }
6074
6075        return false;
6076      }
6077    };
6078
6079    exports = module.exports = keyCode;
6080    /**
6081     * Get by name
6082     *
6083     *   exports.code['enter'] // => 13
6084     */
6085
6086    var codes = exports.code = exports.codes = {
6087      'backspace': 8,
6088      'tab': 9,
6089      'enter': 13,
6090      'shift': 16,
6091      'ctrl': 17,
6092      'alt': 18,
6093      'pause/break': 19,
6094      'caps lock': 20,
6095      'esc': 27,
6096      'space': 32,
6097      'page up': 33,
6098      'page down': 34,
6099      'end': 35,
6100      'home': 36,
6101      'left': 37,
6102      'up': 38,
6103      'right': 39,
6104      'down': 40,
6105      'insert': 45,
6106      'delete': 46,
6107      'command': 91,
6108      'left command': 91,
6109      'right command': 93,
6110      'numpad *': 106,
6111      'numpad +': 107,
6112      'numpad -': 109,
6113      'numpad .': 110,
6114      'numpad /': 111,
6115      'num lock': 144,
6116      'scroll lock': 145,
6117      'my computer': 182,
6118      'my calculator': 183,
6119      ';': 186,
6120      '=': 187,
6121      ',': 188,
6122      '-': 189,
6123      '.': 190,
6124      '/': 191,
6125      '`': 192,
6126      '[': 219,
6127      '\\': 220,
6128      ']': 221,
6129      "'": 222
6130    }; // Helper aliases
6131
6132    var aliases = exports.aliases = {
6133      'windows': 91,
6134      '⇧': 16,
6135      '⌥': 18,
6136      '⌃': 17,
6137      '⌘': 91,
6138      'ctl': 17,
6139      'control': 17,
6140      'option': 18,
6141      'pause': 19,
6142      'break': 19,
6143      'caps': 20,
6144      'return': 13,
6145      'escape': 27,
6146      'spc': 32,
6147      'spacebar': 32,
6148      'pgup': 33,
6149      'pgdn': 34,
6150      'ins': 45,
6151      'del': 46,
6152      'cmd': 91
6153    };
6154    /*!
6155     * Programatically add the following
6156     */
6157    // lower case chars
6158
6159    for (i = 97; i < 123; i++) {
6160      codes[String.fromCharCode(i)] = i - 32;
6161    } // numbers
6162
6163
6164    for (var i = 48; i < 58; i++) {
6165      codes[i - 48] = i;
6166    } // function keys
6167
6168
6169    for (i = 1; i < 13; i++) {
6170      codes['f' + i] = i + 111;
6171    } // numpad keys
6172
6173
6174    for (i = 0; i < 10; i++) {
6175      codes['numpad ' + i] = i + 96;
6176    }
6177    /**
6178     * Get by code
6179     *
6180     *   exports.name[13] // => 'Enter'
6181     */
6182
6183
6184    var names = exports.names = exports.title = {}; // title for backward compat
6185    // Create reverse mapping
6186
6187    for (i in codes) {
6188      names[codes[i]] = i;
6189    } // Add aliases
6190
6191
6192    for (var alias in aliases) {
6193      codes[alias] = aliases[alias];
6194    }
6195  });
6196  keycode.code;
6197  keycode.codes;
6198  keycode.aliases;
6199  keycode.names;
6200  keycode.title;
6201
6202  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
6203  /**
6204   * The `ModalDialog` displays over the video and its controls, which blocks
6205   * interaction with the player until it is closed.
6206   *
6207   * Modal dialogs include a "Close" button and will close when that button
6208   * is activated - or when ESC is pressed anywhere.
6209   *
6210   * @extends Component
6211   */
6212
6213  var ModalDialog = /*#__PURE__*/function (_Component) {
6214    inheritsLoose(ModalDialog, _Component);
6215
6216    /**
6217     * Create an instance of this class.
6218     *
6219     * @param {Player} player
6220     *        The `Player` that this class should be attached to.
6221     *
6222     * @param {Object} [options]
6223     *        The key/value store of player options.
6224     *
6225     * @param {Mixed} [options.content=undefined]
6226     *        Provide customized content for this modal.
6227     *
6228     * @param {string} [options.description]
6229     *        A text description for the modal, primarily for accessibility.
6230     *
6231     * @param {boolean} [options.fillAlways=false]
6232     *        Normally, modals are automatically filled only the first time
6233     *        they open. This tells the modal to refresh its content
6234     *        every time it opens.
6235     *
6236     * @param {string} [options.label]
6237     *        A text label for the modal, primarily for accessibility.
6238     *
6239     * @param {boolean} [options.pauseOnOpen=true]
6240     *        If `true`, playback will will be paused if playing when
6241     *        the modal opens, and resumed when it closes.
6242     *
6243     * @param {boolean} [options.temporary=true]
6244     *        If `true`, the modal can only be opened once; it will be
6245     *        disposed as soon as it's closed.
6246     *
6247     * @param {boolean} [options.uncloseable=false]
6248     *        If `true`, the user will not be able to close the modal
6249     *        through the UI in the normal ways. Programmatic closing is
6250     *        still possible.
6251     */
6252    function ModalDialog(player, options) {
6253      var _this;
6254
6255      _this = _Component.call(this, player, options) || this;
6256
6257      _this.handleKeyDown_ = function (e) {
6258        return _this.handleKeyDown(e);
6259      };
6260
6261      _this.close_ = function (e) {
6262        return _this.close(e);
6263      };
6264
6265      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
6266
6267      _this.closeable(!_this.options_.uncloseable);
6268
6269      _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
6270      // because we only want the contents of the modal in the contentEl
6271      // (not the UI elements like the close button).
6272
6273
6274      _this.contentEl_ = createEl('div', {
6275        className: MODAL_CLASS_NAME + "-content"
6276      }, {
6277        role: 'document'
6278      });
6279      _this.descEl_ = createEl('p', {
6280        className: MODAL_CLASS_NAME + "-description vjs-control-text",
6281        id: _this.el().getAttribute('aria-describedby')
6282      });
6283      textContent(_this.descEl_, _this.description());
6284
6285      _this.el_.appendChild(_this.descEl_);
6286
6287      _this.el_.appendChild(_this.contentEl_);
6288
6289      return _this;
6290    }
6291    /**
6292     * Create the `ModalDialog`'s DOM element
6293     *
6294     * @return {Element}
6295     *         The DOM element that gets created.
6296     */
6297
6298
6299    var _proto = ModalDialog.prototype;
6300
6301    _proto.createEl = function createEl() {
6302      return _Component.prototype.createEl.call(this, 'div', {
6303        className: this.buildCSSClass(),
6304        tabIndex: -1
6305      }, {
6306        'aria-describedby': this.id() + "_description",
6307        'aria-hidden': 'true',
6308        'aria-label': this.label(),
6309        'role': 'dialog'
6310      });
6311    };
6312
6313    _proto.dispose = function dispose() {
6314      this.contentEl_ = null;
6315      this.descEl_ = null;
6316      this.previouslyActiveEl_ = null;
6317
6318      _Component.prototype.dispose.call(this);
6319    }
6320    /**
6321     * Builds the default DOM `className`.
6322     *
6323     * @return {string}
6324     *         The DOM `className` for this object.
6325     */
6326    ;
6327
6328    _proto.buildCSSClass = function buildCSSClass() {
6329      return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
6330    }
6331    /**
6332     * Returns the label string for this modal. Primarily used for accessibility.
6333     *
6334     * @return {string}
6335     *         the localized or raw label of this modal.
6336     */
6337    ;
6338
6339    _proto.label = function label() {
6340      return this.localize(this.options_.label || 'Modal Window');
6341    }
6342    /**
6343     * Returns the description string for this modal. Primarily used for
6344     * accessibility.
6345     *
6346     * @return {string}
6347     *         The localized or raw description of this modal.
6348     */
6349    ;
6350
6351    _proto.description = function description() {
6352      var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
6353
6354      if (this.closeable()) {
6355        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
6356      }
6357
6358      return desc;
6359    }
6360    /**
6361     * Opens the modal.
6362     *
6363     * @fires ModalDialog#beforemodalopen
6364     * @fires ModalDialog#modalopen
6365     */
6366    ;
6367
6368    _proto.open = function open() {
6369      if (!this.opened_) {
6370        var player = this.player();
6371        /**
6372          * Fired just before a `ModalDialog` is opened.
6373          *
6374          * @event ModalDialog#beforemodalopen
6375          * @type {EventTarget~Event}
6376          */
6377
6378        this.trigger('beforemodalopen');
6379        this.opened_ = true; // Fill content if the modal has never opened before and
6380        // never been filled.
6381
6382        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
6383          this.fill();
6384        } // If the player was playing, pause it and take note of its previously
6385        // playing state.
6386
6387
6388        this.wasPlaying_ = !player.paused();
6389
6390        if (this.options_.pauseOnOpen && this.wasPlaying_) {
6391          player.pause();
6392        }
6393
6394        this.on('keydown', this.handleKeyDown_); // Hide controls and note if they were enabled.
6395
6396        this.hadControls_ = player.controls();
6397        player.controls(false);
6398        this.show();
6399        this.conditionalFocus_();
6400        this.el().setAttribute('aria-hidden', 'false');
6401        /**
6402          * Fired just after a `ModalDialog` is opened.
6403          *
6404          * @event ModalDialog#modalopen
6405          * @type {EventTarget~Event}
6406          */
6407
6408        this.trigger('modalopen');
6409        this.hasBeenOpened_ = true;
6410      }
6411    }
6412    /**
6413     * If the `ModalDialog` is currently open or closed.
6414     *
6415     * @param  {boolean} [value]
6416     *         If given, it will open (`true`) or close (`false`) the modal.
6417     *
6418     * @return {boolean}
6419     *         the current open state of the modaldialog
6420     */
6421    ;
6422
6423    _proto.opened = function opened(value) {
6424      if (typeof value === 'boolean') {
6425        this[value ? 'open' : 'close']();
6426      }
6427
6428      return this.opened_;
6429    }
6430    /**
6431     * Closes the modal, does nothing if the `ModalDialog` is
6432     * not open.
6433     *
6434     * @fires ModalDialog#beforemodalclose
6435     * @fires ModalDialog#modalclose
6436     */
6437    ;
6438
6439    _proto.close = function close() {
6440      if (!this.opened_) {
6441        return;
6442      }
6443
6444      var player = this.player();
6445      /**
6446        * Fired just before a `ModalDialog` is closed.
6447        *
6448        * @event ModalDialog#beforemodalclose
6449        * @type {EventTarget~Event}
6450        */
6451
6452      this.trigger('beforemodalclose');
6453      this.opened_ = false;
6454
6455      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6456        player.play();
6457      }
6458
6459      this.off('keydown', this.handleKeyDown_);
6460
6461      if (this.hadControls_) {
6462        player.controls(true);
6463      }
6464
6465      this.hide();
6466      this.el().setAttribute('aria-hidden', 'true');
6467      /**
6468        * Fired just after a `ModalDialog` is closed.
6469        *
6470        * @event ModalDialog#modalclose
6471        * @type {EventTarget~Event}
6472        */
6473
6474      this.trigger('modalclose');
6475      this.conditionalBlur_();
6476
6477      if (this.options_.temporary) {
6478        this.dispose();
6479      }
6480    }
6481    /**
6482     * Check to see if the `ModalDialog` is closeable via the UI.
6483     *
6484     * @param  {boolean} [value]
6485     *         If given as a boolean, it will set the `closeable` option.
6486     *
6487     * @return {boolean}
6488     *         Returns the final value of the closable option.
6489     */
6490    ;
6491
6492    _proto.closeable = function closeable(value) {
6493      if (typeof value === 'boolean') {
6494        var closeable = this.closeable_ = !!value;
6495        var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
6496
6497        if (closeable && !close) {
6498          // The close button should be a child of the modal - n
6498ot its
6499          // content element, so temporarily change the content element.
6500          var temp = this.contentEl_;
6501          this.contentEl_ = this.el_;
6502          close = this.addChild('closeButton', {
6503            controlText: 'Close Modal Dialog'
6504          });
6505          this.contentEl_ = temp;
6506          this.on(close, 'close', this.close_);
6507        } // If this is being made uncloseable and has a close button, remove it.
6508
6509
6510        if (!closeable && close) {
6511          this.off(close, 'close', this.close_);
6512          this.removeChild(close);
6513          close.dispose();
6514        }
6515      }
6516
6517      return this.closeable_;
6518    }
6519    /**
6520     * Fill the modal's content element with the modal's "content" option.
6521     * The content element will be emptied before this change takes place.
6522     */
6523    ;
6524
6525    _proto.fill = function fill() {
6526      this.fillWith(this.content());
6527    }
6528    /**
6529     * Fill the modal's content element with arbitrary content.
6530     * The content element will be emptied before this change takes place.
6531     *
6532     * @fires ModalDialog#beforemodalfill
6533     * @fires ModalDialog#modalfill
6534     *
6535     * @param {Mixed} [content]
6536     *        The same rules apply to this as apply to the `content` option.
6537     */
6538    ;
6539
6540    _proto.fillWith = function fillWith(content) {
6541      var contentEl = this.contentEl();
6542      var parentEl = contentEl.parentNode;
6543      var nextSiblingEl = contentEl.nextSibling;
6544      /**
6545        * Fired just before a `ModalDialog` is filled with content.
6546        *
6547        * @event ModalDialog#beforemodalfill
6548        * @type {EventTarget~Event}
6549        */
6550
6551      this.trigger('beforemodalfill');
6552      this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
6553      // manipulation to avoid modifying the live DOM multiple times.
6554
6555      parentEl.removeChild(contentEl);
6556      this.empty();
6557      insertContent(contentEl, content);
6558      /**
6559       * Fired just after a `ModalDialog` is filled with content.
6560       *
6561       * @event ModalDialog#modalfill
6562       * @type {EventTarget~Event}
6563       */
6564
6565      this.trigger('modalfill'); // Re-inject the re-filled content element.
6566
6567      if (nextSiblingEl) {
6568        parentEl.insertBefore(contentEl, nextSiblingEl);
6569      } else {
6570        parentEl.appendChild(contentEl);
6571      } // make sure that the close button is last in the dialog DOM
6572
6573
6574      var closeButton = this.getChild('closeButton');
6575
6576      if (closeButton) {
6577        parentEl.appendChild(closeButton.el_);
6578      }
6579    }
6580    /**
6581     * Empties the content element. This happens anytime the modal is filled.
6582     *
6583     * @fires ModalDialog#beforemodalempty
6584     * @fires ModalDialog#modalempty
6585     */
6586    ;
6587
6588    _proto.empty = function empty() {
6589      /**
6590      * Fired just before a `ModalDialog` is emptied.
6591      *
6592      * @event ModalDialog#beforemodalempty
6593      * @type {EventTarget~Event}
6594      */
6595      this.trigger('beforemodalempty');
6596      emptyEl(this.contentEl());
6597      /**
6598      * Fired just after a `ModalDialog` is emptied.
6599      *
6600      * @event ModalDialog#modalempty
6601      * @type {EventTarget~Event}
6602      */
6603
6604      this.trigger('modalempty');
6605    }
6606    /**
6607     * Gets or sets the modal content, which gets normalized before being
6608     * rendered into the DOM.
6609     *
6610     * This does not update the DOM or fill the modal, but it is called during
6611     * that process.
6612     *
6613     * @param  {Mixed} [value]
6614     *         If defined, sets the internal content value to be used on the
6615     *         next call(s) to `fill`. This value is normalized before being
6616     *         inserted. To "clear" the internal content value, pass `null`.
6617     *
6618     * @return {Mixed}
6619     *         The current content of the modal dialog
6620     */
6621    ;
6622
6623    _proto.content = function content(value) {
6624      if (typeof value !== 'undefined') {
6625        this.content_ = value;
6626      }
6627
6628      return this.content_;
6629    }
6630    /**
6631     * conditionally focus the modal dialog if focus was previously on the player.
6632     *
6633     * @private
6634     */
6635    ;
6636
6637    _proto.conditionalFocus_ = function conditionalFocus_() {
6638      var activeEl = document_1.activeElement;
6639      var playerEl = this.player_.el_;
6640      this.previouslyActiveEl_ = null;
6641
6642      if (playerEl.contains(activeEl) || playerEl === activeEl) {
6643        this.previouslyActiveEl_ = activeEl;
6644        this.focus();
6645      }
6646    }
6647    /**
6648     * conditionally blur the element and refocus the last focused element
6649     *
6650     * @private
6651     */
6652    ;
6653
6654    _proto.conditionalBlur_ = function conditionalBlur_() {
6655      if (this.previouslyActiveEl_) {
6656        this.previouslyActiveEl_.focus();
6657        this.previouslyActiveEl_ = null;
6658      }
6659    }
6660    /**
6661     * Keydown handler. Attached when modal is focused.
6662     *
6663     * @listens keydown
6664     */
6665    ;
6666
6667    _proto.handleKeyDown = function handleKeyDown(event) {
6668      // Do not allow keydowns to reach out of the modal dialog.
6669      event.stopPropagation();
6670
6671      if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6672        event.preventDefault();
6673        this.close();
6674        return;
6675      } // exit early if it isn't a tab key
6676
6677
6678      if (!keycode.isEventKey(event, 'Tab')) {
6679        return;
6680      }
6681
6682      var focusableEls = this.focusableEls_();
6683      var activeEl = this.el_.querySelector(':focus');
6684      var focusIndex;
6685
6686      for (var i = 0; i < focusableEls.length; i++) {
6687        if (activeEl === focusableEls[i]) {
6688          focusIndex = i;
6689          break;
6690        }
6691      }
6692
6693      if (document_1.activeElement === this.el_) {
6694        focusIndex = 0;
6695      }
6696
6697      if (event.shiftKey && focusIndex === 0) {
6698        focusableEls[focusableEls.length - 1].focus();
6699        event.preventDefault();
6700      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6701        focusableEls[0].focus();
6702        event.preventDefault();
6703      }
6704    }
6705    /**
6706     * get all focusable elements
6707     *
6708     * @private
6709     */
6710    ;
6711
6712    _proto.focusableEls_ = function focusableEls_() {
6713      var allChildren = this.el_.querySelectorAll('*');
6714      return Array.prototype.filter.call(allChildren, function (child) {
6715        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');
6716      });
6717    };
6718
6719    return ModalDialog;
6720  }(Component$1);
6721  /**
6722   * Default options for `ModalDialog` default options.
6723   *
6724   * @type {Object}
6725   * @private
6726   */
6727
6728
6729  ModalDialog.prototype.options_ = {
6730    pauseOnOpen: true,
6731    temporary: true
6732  };
6733  Component$1.registerComponent('ModalDialog', ModalDialog);
6734
6735  /**
6736   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6737   * {@link VideoTrackList}
6738   *
6739   * @extends EventTarget
6740   */
6741
6742  var TrackList = /*#__PURE__*/function (_EventTarget) {
6743    inheritsLoose(TrackList, _EventTarget);
6744
6745    /**
6746     * Create an instance of this class
6747     *
6748     * @param {Track[]} tracks
6749     *        A list of tracks to initialize the list with.
6750     *
6751     * @abstract
6752     */
6753    function TrackList(tracks) {
6754      var _this;
6755
6756      if (tracks === void 0) {
6757        tracks = [];
6758      }
6759
6760      _this = _EventTarget.call(this) || this;
6761      _this.tracks_ = [];
6762      /**
6763       * @memberof TrackList
6764       * @member {number} length
6765       *         The current number of `Track`s in the this Trackist.
6766       * @instance
6767       */
6768
6769      Object.defineProperty(assertThisInitialized(_this), 'length', {
6770        get: function get() {
6771          return this.tracks_.length;
6772        }
6773      });
6774
6775      for (var i = 0; i < tracks.length; i++) {
6776        _this.addTrack(tracks[i]);
6777      }
6778
6779      return _this;
6780    }
6781    /**
6782     * Add a {@link Track} to the `TrackList`
6783     *
6784     * @param {Track} track
6785     *        The audio, video, or text track to add to the list.
6786     *
6787     * @fires TrackList#addtrack
6788     */
6789
6790
6791    var _proto = TrackList.prototype;
6792
6793    _proto.addTrack = function addTrack(track) {
6794      var _this2 = this;
6795
6796      var index = this.tracks_.length;
6797
6798      if (!('' + index in this)) {
6799        Object.defineProperty(this, index, {
6800          get: function get() {
6801            return this.tracks_[index];
6802          }
6803        });
6804      } // Do not add duplicate tracks
6805
6806
6807      if (this.tracks_.indexOf(track) === -1) {
6808        this.tracks_.push(track);
6809        /**
6810         * Triggered when a track is added to a track list.
6811         *
6812         * @event TrackList#addtrack
6813         * @type {EventTarget~Event}
6814         * @property {Track} track
6815         *           A reference to track that was added.
6816         */
6817
6818        this.trigger({
6819          track: track,
6820          type: 'addtrack',
6821          target: this
6822        });
6823      }
6824      /**
6825       * Triggered when a track label is changed.
6826       *
6827       * @event TrackList#addtrack
6828       * @type {EventTarget~Event}
6829       * @property {Track} track
6830       *           A reference to track that was added.
6831       */
6832
6833
6834      track.labelchange_ = function () {
6835        _this2.trigger({
6836          track: track,
6837          type: 'labelchange',
6838          target: _this2
6839        });
6840      };
6841
6842      if (isEvented(track)) {
6843        track.addEventListener('labelchange', track.labelchange_);
6844      }
6845    }
6846    /**
6847     * Remove a {@link Track} from the `TrackList`
6848     *
6849     * @param {Track} rtrack
6850     *        The audio, video, or text track to remove from the list.
6851     *
6852     * @fires TrackList#removetrack
6853     */
6854    ;
6855
6856    _proto.removeTrack = function removeTrack(rtrack) {
6857      var track;
6858
6859      for (var i = 0, l = this.length; i < l; i++) {
6860        if (this[i] === rtrack) {
6861          track = this[i];
6862
6863          if (track.off) {
6864            track.off();
6865          }
6866
6867          this.tracks_.splice(i, 1);
6868          break;
6869        }
6870      }
6871
6872      if (!track) {
6873        return;
6874      }
6875      /**
6876       * Triggered when a track is removed from track list.
6877       *
6878       * @event TrackList#removetrack
6879       * @type {EventTarget~Event}
6880       * @property {Track} track
6881       *           A reference to track that was removed.
6882       */
6883
6884
6885      this.trigger({
6886        track: track,
6887        type: 'removetrack',
6888        target: this
6889      });
6890    }
6891    /**
6892     * Get a Track from the TrackList by a tracks id
6893     *
6894     * @param {string} id - the id of the track to get
6895     * @method getTrackById
6896     * @return {Track}
6897     * @private
6898     */
6899    ;
6900
6901    _proto.getTrackById = function getTrackById(id) {
6902      var result = null;
6903
6904      for (var i = 0, l = this.length; i < l; i++) {
6905        var track = this[i];
6906
6907        if (track.id === id) {
6908          result = track;
6909          break;
6910        }
6911      }
6912
6913      return result;
6914    };
6915
6916    return TrackList;
6917  }(EventTarget$2);
6918  /**
6919   * Triggered when a different track is selected/enabled.
6920   *
6921   * @event TrackList#change
6922   * @type {EventTarget~Event}
6923   */
6924
6925  /**
6926   * Events that can be called with on + eventName. See {@link EventHandler}.
6927   *
6928   * @property {Object} TrackList#allowedEvents_
6929   * @private
6930   */
6931
6932
6933  TrackList.prototype.allowedEvents_ = {
6934    change: 'change',
6935    addtrack: 'addtrack',
6936    removetrack: 'removetrack',
6937    labelchange: 'labelchange'
6938  }; // emulate attribute EventHandler support to allow for feature detection
6939
6940  for (var event in TrackList.prototype.allowedEvents_) {
6941    TrackList.prototype['on' + event] = null;
6942  }
6943
6944  /**
6945   * Anywhere we call this function we diverge from the spec
6946   * as we only support one enabled audiotrack at a time
6947   *
6948   * @param {AudioTrackList} list
6949   *        list to work on
6950   *
6951   * @param {AudioTrack} track
6952   *        The track to skip
6953   *
6954   * @private
6955   */
6956
6957  var disableOthers$1 = function disableOthers(list, track) {
6958    for (var i = 0; i < list.length; i++) {
6959      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6960        continue;
6961      } // another audio track is enabled, disable it
6962
6963
6964      list[i].enabled = false;
6965    }
6966  };
6967  /**
6968   * The current list of {@link AudioTrack} for a media file.
6969   *
6970   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6971   * @extends TrackList
6972   */
6973
6974
6975  var AudioTrackList = /*#__PURE__*/function (_TrackList) {
6976    inheritsLoose(AudioTrackList, _TrackList);
6977
6978    /**
6979     * Create an instance of this class.
6980     *
6981     * @param {AudioTrack[]} [tracks=[]]
6982     *        A list of `AudioTrack` to instantiate the list with.
6983     */
6984    function AudioTrackList(tracks) {
6985      var _this;
6986
6987      if (tracks === void 0) {
6988        tracks = [];
6989      }
6990
6991      // make sure only 1 track is enabled
6992      // sorted from last index to first index
6993      for (var i = tracks.length - 1; i >= 0; i--) {
6994        if (tracks[i].enabled) {
6995          disableOthers$1(tracks, tracks[i]);
6996          break;
6997        }
6998      }
6999
7000      _this = _TrackList.call(this, tracks) || this;
7001      _this.changing_ = false;
7002      return _this;
7003    }
7004    /**
7005     * Add an {@link AudioTrack} to the `AudioTrackList`.
7006     *
7007     * @param {AudioTrack} track
7008     *        The AudioTrack to add to the list
7009     *
7010     * @fires TrackList#addtrack
7011     */
7012
7013
7014    var _proto = AudioTrackList.prototype;
7015
7016    _proto.addTrack = function addTrack(track) {
7017      var _this2 = this;
7018
7019      if (track.enabled) {
7020        disableOthers$1(this, track);
7021      }
7022
7023      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
7024
7025
7026      if (!track.addEventListener) {
7027        return;
7028      }
7029
7030      track.enabledChange_ = function () {
7031        // when we are disabling other tracks (since we don't support
7032        // more than one track at a time) we will set changing_
7033        // to true so that we don't trigger additional change events
7034        if (_this2.changing_) {
7035          return;
7036        }
7037
7038        _this2.changing_ = true;
7039        disableOthers$1(_this2, track);
7040        _this2.changing_ = false;
7041
7042        _this2.trigger('change');
7043      };
7044      /**
7045       * @listens AudioTrack#enabledchange
7046       * @fires TrackList#change
7047       */
7048
7049
7050      track.addEventListener('enabledchange', track.enabledChange_);
7051    };
7052
7053    _proto.removeTrack = function removeTrack(rtrack) {
7054      _TrackList.prototype.removeTrack.call(this, rtrack);
7055
7056      if (rtrack.removeEventListener && rtrack.enabledChange_) {
7057        rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
7058        rtrack.enabledChange_ = null;
7059      }
7060    };
7061
7062    return AudioTrackList;
7063  }(TrackList);
7064
7065  /**
7066   * Un-select all other {@link VideoTrack}s that are selected.
7067   *
7068   * @param {VideoTrackList} list
7069   *        list to work on
7070   *
7071   * @param {VideoTrack} track
7072   *        The track to skip
7073   *
7074   * @private
7075   */
7076
7077  var disableOthers = function disableOthers(list, track) {
7078    for (var i = 0; i < list.length; i++) {
7079      if (!Object.keys(list[i]).length || track.id === list[i].id) {
7080        continue;
7081      } // another video track is enabled, disable it
7082
7083
7084      list[i].selected = false;
7085    }
7086  };
7087  /**
7088   * The current list of {@link VideoTrack} for a video.
7089   *
7090   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
7091   * @extends TrackList
7092   */
7093
7094
7095  var VideoTrackList = /*#__PURE__*/function (_TrackList) {
7096    inheritsLoose(VideoTrackList, _TrackList);
7097
7098    /**
7099     * Create an instance of this class.
7100     *
7101     * @param {VideoTrack[]} [tracks=[]]
7102     *        A list of `VideoTrack` to instantiate the list with.
7103     */
7104    function VideoTrackList(tracks) {
7105      var _this;
7106
7107      if (tracks === void 0) {
7108        tracks = [];
7109      }
7110
7111      // make sure only 1 track is enabled
7112      // sorted from last index to first index
7113      for (var i = tracks.length - 1; i >= 0; i--) {
7114        if (tracks[i].selected) {
7115          disableOthers(tracks, tracks[i]);
7116          break;
7117        }
7118      }
7119
7120      _this = _TrackList.call(this, tracks) || this;
7121      _this.changing_ = false;
7122      /**
7123       * @member {number} VideoTrackList#selectedIndex
7124       *         The current index of the selected {@link VideoTrack`}.
7125       */
7126
7127      Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', {
7128        get: function get() {
7129          for (var _i = 0; _i < this.length; _i++) {
7130            if (this[_i].selected) {
7131              return _i;
7132            }
7133          }
7134
7135          return -1;
7136        },
7137        set: function set() {}
7138      });
7139      return _this;
7140    }
7141    /**
7142     * Add a {@link VideoTrack} to the `VideoTrackList`.
7143     *
7144     * @param {VideoTrack} track
7145     *        The VideoTrack to add to the list
7146     *
7147     * @fires TrackList#addtrack
7148     */
7149
7150
7151    var _proto = VideoTrackList.prototype;
7152
7153    _proto.addTrack = function addTrack(track) {
7154      var _this2 = this;
7155
7156      if (track.selected) {
7157        disableOthers(this, track);
7158      }
7159
7160      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
7161
7162
7163      if (!track.addEventListener) {
7164        return;
7165      }
7166
7167      track.selectedChange_ = function () {
7168        if (_this2.changing_) {
7169          return;
7170        }
7171
7172        _this2.changing_ = true;
7173        disableOthers(_this2, track);
7174        _this2.changing_ = false;
7175
7176        _this2.trigger('change');
7177      };
7178      /**
7179       * @listens VideoTrack#selectedchange
7180       * @fires TrackList#change
7181       */
7182
7183
7184      track.addEventListener('selectedchange', track.selectedChange_);
7185    };
7186
7187    _proto.removeTrack = function removeTrack(rtrack) {
7188      _TrackList.prototype.removeTrack.call(this, rtrack);
7189
7190      if (rtrack.removeEventListener && rtrack.selectedChange_) {
7191        rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
7192        rtrack.selectedChange_ = null;
7193      }
7194    };
7195
7196    return VideoTrackList;
7197  }(TrackList);
7198
7199  /**
7200   * The current list of {@link TextTrack} for a media file.
7201   *
7202   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
7203   * @extends TrackList
7204   */
7205
7206  var TextTrackList = /*#__PURE__*/function (_TrackList) {
7207    inheritsLoose(TextTrackList, _TrackList);
7208
7209    function TextTrackList() {
7210      return _TrackList.apply(this, arguments) || this;
7211    }
7212
7213    var _proto = TextTrackList.prototype;
7214
7215    /**
7216     * Add a {@link TextTrack} to the `TextTrackList`
7217     *
7218     * @param {TextTrack} track
7219     *        The text track to add to the list.
7220     *
7221     * @fires TrackList#addtrack
7222     */
7223    _proto.addTrack = function addTrack(track) {
7224      var _this = this;
7225
7226      _TrackList.prototype.addTrack.call(this, track);
7227
7228      if (!this.queueChange_) {
7229        this.queueChange_ = function () {
7230          return _this.queueTrigger('change');
7231        };
7232      }
7233
7234      if (!this.triggerSelectedlanguagechange) {
7235        this.triggerSelectedlanguagechange_ = function () {
7236          return _this.trigger('selectedlanguagechange');
7237        };
7238      }
7239      /**
7240       * @listens TextTrack#modechange
7241       * @fires TrackList#change
7242       */
7243
7244
7245      track.addEventListener('modechange', this.queueChange_);
7246      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
7247
7248      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
7249        track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
7250      }
7251    };
7252
7253    _proto.removeTrack = function removeTrack(rtrack) {
7254      _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
7255
7256
7257      if (rtrack.removeEventListener) {
7258        if (this.queueChange_) {
7259          rtrack.removeEventListener('modechange', this.queueChange_);
7260        }
7261
7262        if (this.selectedlanguagechange_) {
7263          rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
7264        }
7265      }
7266    };
7267
7268    return TextTrackList;
7269  }(TrackList);
7270
7271  /**
7272   * @file html-track-element-list.js
7273   */
7274
7275  /**
7276   * The current list of {@link HtmlTrackElement}s.
7277   */
7278  var HtmlTrackElementList = /*#__PURE__*/function () {
7279    /**
7280     * Create an instance of this class.
7281     *
7282     * @param {HtmlTrackElement[]} [tracks=[]]
7283     *        A list of `HtmlTrackElement` to instantiate the list with.
7284     */
7285    function HtmlTrackElementList(trackElements) {
7286      if (tra
7286ckElements === void 0) {
7287        trackElements = [];
7288      }
7289
7290      this.trackElements_ = [];
7291      /**
7292       * @memberof HtmlTrackElementList
7293       * @member {number} length
7294       *         The current number of `Track`s in the this Trackist.
7295       * @instance
7296       */
7297
7298      Object.defineProperty(this, 'length', {
7299        get: function get() {
7300          return this.trackElements_.length;
7301        }
7302      });
7303
7304      for (var i = 0, length = trackElements.length; i < length; i++) {
7305        this.addTrackElement_(trackElements[i]);
7306      }
7307    }
7308    /**
7309     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
7310     *
7311     * @param {HtmlTrackElement} trackElement
7312     *        The track element to add to the list.
7313     *
7314     * @private
7315     */
7316
7317
7318    var _proto = HtmlTrackElementList.prototype;
7319
7320    _proto.addTrackElement_ = function addTrackElement_(trackElement) {
7321      var index = this.trackElements_.length;
7322
7323      if (!('' + index in this)) {
7324        Object.defineProperty(this, index, {
7325          get: function get() {
7326            return this.trackElements_[index];
7327          }
7328        });
7329      } // Do not add duplicate elements
7330
7331
7332      if (this.trackElements_.indexOf(trackElement) === -1) {
7333        this.trackElements_.push(trackElement);
7334      }
7335    }
7336    /**
7337     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
7338     * {@link TextTrack}.
7339     *
7340     * @param {TextTrack} track
7341     *        The track associated with a track element.
7342     *
7343     * @return {HtmlTrackElement|undefined}
7344     *         The track element that was found or undefined.
7345     *
7346     * @private
7347     */
7348    ;
7349
7350    _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
7351      var trackElement_;
7352
7353      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
7354        if (track === this.trackElements_[i].track) {
7355          trackElement_ = this.trackElements_[i];
7356          break;
7357        }
7358      }
7359
7360      return trackElement_;
7361    }
7362    /**
7363     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
7364     *
7365     * @param {HtmlTrackElement} trackElement
7366     *        The track element to remove from the list.
7367     *
7368     * @private
7369     */
7370    ;
7371
7372    _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
7373      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
7374        if (trackElement === this.trackElements_[i]) {
7375          if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
7376            this.trackElements_[i].track.off();
7377          }
7378
7379          if (typeof this.trackElements_[i].off === 'function') {
7380            this.trackElements_[i].off();
7381          }
7382
7383          this.trackElements_.splice(i, 1);
7384          break;
7385        }
7386      }
7387    };
7388
7389    return HtmlTrackElementList;
7390  }();
7391
7392  /**
7393   * @file text-track-cue-list.js
7394   */
7395
7396  /**
7397   * @typedef {Object} TextTrackCueList~TextTrackCue
7398   *
7399   * @property {string} id
7400   *           The unique id for this text track cue
7401   *
7402   * @property {number} startTime
7403   *           The start time for this text track cue
7404   *
7405   * @property {number} endTime
7406   *           The end time for this text track cue
7407   *
7408   * @property {boolean} pauseOnExit
7409   *           Pause when the end time is reached if true.
7410   *
7411   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
7412   */
7413
7414  /**
7415   * A List of TextTrackCues.
7416   *
7417   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
7418   */
7419  var TextTrackCueList = /*#__PURE__*/function () {
7420    /**
7421     * Create an instance of this class..
7422     *
7423     * @param {Array} cues
7424     *        A list of cues to be initialized with
7425     */
7426    function TextTrackCueList(cues) {
7427      TextTrackCueList.prototype.setCues_.call(this, cues);
7428      /**
7429       * @memberof TextTrackCueList
7430       * @member {number} length
7431       *         The current number of `TextTrackCue`s in the TextTrackCueList.
7432       * @instance
7433       */
7434
7435      Object.defineProperty(this, 'length', {
7436        get: function get() {
7437          return this.length_;
7438        }
7439      });
7440    }
7441    /**
7442     * A setter for cues in this list. Creates getters
7443     * an an index for the cues.
7444     *
7445     * @param {Array} cues
7446     *        An array of cues to set
7447     *
7448     * @private
7449     */
7450
7451
7452    var _proto = TextTrackCueList.prototype;
7453
7454    _proto.setCues_ = function setCues_(cues) {
7455      var oldLength = this.length || 0;
7456      var i = 0;
7457      var l = cues.length;
7458      this.cues_ = cues;
7459      this.length_ = cues.length;
7460
7461      var defineProp = function defineProp(index) {
7462        if (!('' + index in this)) {
7463          Object.defineProperty(this, '' + index, {
7464            get: function get() {
7465              return this.cues_[index];
7466            }
7467          });
7468        }
7469      };
7470
7471      if (oldLength < l) {
7472        i = oldLength;
7473
7474        for (; i < l; i++) {
7475          defineProp.call(this, i);
7476        }
7477      }
7478    }
7479    /**
7480     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
7481     *
7482     * @param {string} id
7483     *        The id of the cue that should be searched for.
7484     *
7485     * @return {TextTrackCueList~TextTrackCue|null}
7486     *         A single cue or null if none was found.
7487     */
7488    ;
7489
7490    _proto.getCueById = function getCueById(id) {
7491      var result = null;
7492
7493      for (var i = 0, l = this.length; i < l; i++) {
7494        var cue = this[i];
7495
7496        if (cue.id === id) {
7497          result = cue;
7498          break;
7499        }
7500      }
7501
7502      return result;
7503    };
7504
7505    return TextTrackCueList;
7506  }();
7507
7508  /**
7509   * @file track-kinds.js
7510   */
7511
7512  /**
7513   * All possible `VideoTrackKind`s
7514   *
7515   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7516   * @typedef VideoTrack~Kind
7517   * @enum
7518   */
7519  var VideoTrackKind = {
7520    alternative: 'alternative',
7521    captions: 'captions',
7522    main: 'main',
7523    sign: 'sign',
7524    subtitles: 'subtitles',
7525    commentary: 'commentary'
7526  };
7527  /**
7528   * All possible `AudioTrackKind`s
7529   *
7530   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7531   * @typedef AudioTrack~Kind
7532   * @enum
7533   */
7534
7535  var AudioTrackKind = {
7536    'alternative': 'alternative',
7537    'descriptions': 'descriptions',
7538    'main': 'main',
7539    'main-desc': 'main-desc',
7540    'translation': 'translation',
7541    'commentary': 'commentary'
7542  };
7543  /**
7544   * All possible `TextTrackKind`s
7545   *
7546   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7547   * @typedef TextTrack~Kind
7548   * @enum
7549   */
7550
7551  var TextTrackKind = {
7552    subtitles: 'subtitles',
7553    captions: 'captions',
7554    descriptions: 'descriptions',
7555    chapters: 'chapters',
7556    metadata: 'metadata'
7557  };
7558  /**
7559   * All possible `TextTrackMode`s
7560   *
7561   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7562   * @typedef TextTrack~Mode
7563   * @enum
7564   */
7565
7566  var TextTrackMode = {
7567    disabled: 'disabled',
7568    hidden: 'hidden',
7569    showing: 'showing'
7570  };
7571
7572  /**
7573   * A Track class that contains all of the common functionality for {@link AudioTrack},
7574   * {@link VideoTrack}, and {@link TextTrack}.
7575   *
7576   * > Note: This class should not be used directly
7577   *
7578   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7579   * @extends EventTarget
7580   * @abstract
7581   */
7582
7583  var Track = /*#__PURE__*/function (_EventTarget) {
7584    inheritsLoose(Track, _EventTarget);
7585
7586    /**
7587     * Create an instance of this class.
7588     *
7589     * @param {Object} [options={}]
7590     *        Object of option names and values
7591     *
7592     * @param {string} [options.kind='']
7593     *        A valid kind for the track type you are creating.
7594     *
7595     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7596     *        A unique id for this AudioTrack.
7597     *
7598     * @param {string} [options.label='']
7599     *        The menu label for this track.
7600     *
7601     * @param {string} [options.language='']
7602     *        A valid two character language code.
7603     *
7604     * @abstract
7605     */
7606    function Track(options) {
7607      var _this;
7608
7609      if (options === void 0) {
7610        options = {};
7611      }
7612
7613      _this = _EventTarget.call(this) || this;
7614      var trackProps = {
7615        id: options.id || 'vjs_track_' + newGUID(),
7616        kind: options.kind || '',
7617        language: options.language || ''
7618      };
7619      var label = options.label || '';
7620      /**
7621       * @memberof Track
7622       * @member {string} id
7623       *         The id of this track. Cannot be changed after creation.
7624       * @instance
7625       *
7626       * @readonly
7627       */
7628
7629      /**
7630       * @memberof Track
7631       * @member {string} kind
7632       *         The kind of track that this is. Cannot be changed after creation.
7633       * @instance
7634       *
7635       * @readonly
7636       */
7637
7638      /**
7639       * @memberof Track
7640       * @member {string} language
7641       *         The two letter language code for this track. Cannot be changed after
7642       *         creation.
7643       * @instance
7644       *
7645       * @readonly
7646       */
7647
7648      var _loop = function _loop(key) {
7649        Object.defineProperty(assertThisInitialized(_this), key, {
7650          get: function get() {
7651            return trackProps[key];
7652          },
7653          set: function set() {}
7654        });
7655      };
7656
7657      for (var key in trackProps) {
7658        _loop(key);
7659      }
7660      /**
7661       * @memberof Track
7662       * @member {string} label
7663       *         The label of this track. Cannot be changed after creation.
7664       * @instance
7665       *
7666       * @fires Track#labelchange
7667       */
7668
7669
7670      Object.defineProperty(assertThisInitialized(_this), 'label', {
7671        get: function get() {
7672          return label;
7673        },
7674        set: function set(newLabel) {
7675          if (newLabel !== label) {
7676            label = newLabel;
7677            /**
7678             * An event that fires when label changes on this track.
7679             *
7680             * > Note: This is not part of the spec!
7681             *
7682             * @event Track#labelchange
7683             * @type {EventTarget~Event}
7684             */
7685
7686            this.trigger('labelchange');
7687          }
7688        }
7689      });
7690      return _this;
7691    }
7692
7693    return Track;
7694  }(EventTarget$2);
7695
7696  /**
7697   * @file url.js
7698   * @module url
7699   */
7700  /**
7701   * @typedef {Object} url:URLObject
7702   *
7703   * @property {string} protocol
7704   *           The protocol of the url that was parsed.
7705   *
7706   * @property {string} hostname
7707   *           The hostname of the url that was parsed.
7708   *
7709   * @property {string} port
7710   *           The port of the url that was parsed.
7711   *
7712   * @property {string} pathname
7713   *           The pathname of the url that was parsed.
7714   *
7715   * @property {string} search
7716   *           The search query of the url that was parsed.
7717   *
7718   * @property {string} hash
7719   *           The hash of the url that was parsed.
7720   *
7721   * @property {string} host
7722   *           The host of the url that was parsed.
7723   */
7724
7725  /**
7726   * Resolve and parse the elements of a URL.
7727   *
7728   * @function
7729   * @param    {String} url
7730   *           The url to parse
7731   *
7732   * @return   {url:URLObject}
7733   *           An object of url details
7734   */
7735
7736  var parseUrl = function parseUrl(url) {
7737    // This entire method can be replace with URL once we are able to drop IE11
7738    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
7739
7740    var a = document_1.createElement('a');
7741    a.href = url; // Copy the specific URL properties to a new object
7742    // This is also needed for IE because the anchor loses its
7743    // properties when it's removed from the dom
7744
7745    var details = {};
7746
7747    for (var i = 0; i < props.length; i++) {
7748      details[props[i]] = a[props[i]];
7749    } // IE adds the port to the host property unlike everyone else. If
7750    // a port identifier is added for standard ports, strip it.
7751
7752
7753    if (details.protocol === 'http:') {
7754      details.host = details.host.replace(/:80$/, '');
7755    }
7756
7757    if (details.protocol === 'https:') {
7758      details.host = details.host.replace(/:443$/, '');
7759    }
7760
7761    if (!details.protocol) {
7762      details.protocol = window_1.location.protocol;
7763    }
7764    /* istanbul ignore if */
7765
7766
7767    if (!details.host) {
7768      details.host = window_1.location.host;
7769    }
7770
7771    return details;
7772  };
7773  /**
7774   * Get absolute version of relative URL. Used to tell Flash the correct URL.
7775   *
7776   * @function
7777   * @param    {string} url
7778   *           URL to make absolute
7779   *
7780   * @return   {string}
7781   *           Absolute URL
7782   *
7783   * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7784   */
7785
7786  var getAbsoluteURL = function getAbsoluteURL(url) {
7787    // Check if absolute URL
7788    if (!url.match(/^https?:\/\//)) {
7789      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
7790      // add the url to an anchor and let the browser parse the URL
7791      var a = document_1.createElement('a');
7792      a.href = url;
7793      url = a.href;
7794    }
7795
7796    return url;
7797  };
7798  /**
7799   * Returns the extension of the passed file name. It will return an empty string
7800   * if passed an invalid path.
7801   *
7802   * @function
7803   * @param    {string} path
7804   *           The fileName path like '/path/to/file.mp4'
7805   *
7806   * @return  {string}
7807   *           The extension in lower case or an empty string if no
7808   *           extension could be found.
7809   */
7810
7811  var getFileExtension = function getFileExtension(path) {
7812    if (typeof path === 'string') {
7813      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7814      var pathParts = splitPathRe.exec(path);
7815
7816      if (pathParts) {
7817        return pathParts.pop().toLowerCase();
7818      }
7819    }
7820
7821    return '';
7822  };
7823  /**
7824   * Returns whether the url passed is a cross domain request or not.
7825   *
7826   * @function
7827   * @param    {string} url
7828   *           The url to check.
7829   *
7830   * @param    {Object} [winLoc]
7831   *           the domain to check the url against, defaults to window.location
7832   *
7833   * @param    {string} [winLoc.protocol]
7834   *           The window location protocol defaults to window.location.protocol
7835   *
7836   * @param    {string} [winLoc.host]
7837   *           The window location host defaults to window.location.host
7838   *
7839   * @return   {boolean}
7840   *           Whether it is a cross domain request or not.
7841   */
7842
7843  var isCrossOrigin = function isCrossOrigin(url, winLoc) {
7844    if (winLoc === void 0) {
7845      winLoc = window_1.location;
7846    }
7847
7848    var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
7849
7850    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
7851    // IE8 doesn't know location.origin, so we won't rely on it here
7852
7853    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7854    return crossOrigin;
7855  };
7856
7857  var Url = /*#__PURE__*/Object.freeze({
7858    __proto__: null,
7859    parseUrl: parseUrl,
7860    getAbsoluteURL: getAbsoluteURL,
7861    getFileExtension: getFileExtension,
7862    isCrossOrigin: isCrossOrigin
7863  });
7864
7865  var isFunction_1 = isFunction;
7866  var toString = Object.prototype.toString;
7867
7868  function isFunction(fn) {
7869    if (!fn) {
7870      return false;
7871    }
7872
7873    var string = toString.call(fn);
7874    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
7875    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7876  }
7877
7878  var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
7879    if (decodeResponseBody === void 0) {
7880      decodeResponseBody = false;
7881    }
7882
7883    return function (err, response, responseBody) {
7884      // if the XHR failed, return that error
7885      if (err) {
7886        callback(err);
7887        return;
7888      } // if the HTTP status code is 4xx or 5xx, the request also failed
7889
7890
7891      if (response.statusCode >= 400 && response.statusCode <= 599) {
7892        var cause = responseBody;
7893
7894        if (decodeResponseBody) {
7895          if (window_1.TextDecoder) {
7896            var charset = getCharset(response.headers && response.headers['content-type']);
7897
7898            try {
7899              cause = new TextDecoder(charset).decode(responseBody);
7900            } catch (e) {}
7901          } else {
7902            cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
7903          }
7904        }
7905
7906        callback({
7907          cause: cause
7908        });
7909        return;
7910      } // otherwise, request succeeded
7911
7912
7913      callback(null, responseBody);
7914    };
7915  };
7916
7917  function getCharset(contentTypeHeader) {
7918    if (contentTypeHeader === void 0) {
7919      contentTypeHeader = '';
7920    }
7921
7922    return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
7923      var _contentType$split = contentType.split('='),
7924          type = _contentType$split[0],
7925          value = _contentType$split[1];
7926
7927      if (type.trim() === 'charset') {
7928        return value.trim();
7929      }
7930
7931      return charset;
7932    }, 'utf-8');
7933  }
7934
7935  var httpHandler = httpResponseHandler;
7936
7937  createXHR.httpHandler = httpHandler;
7938  /**
7939   * @license
7940   * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
7941   * Copyright (c) 2014 David Björklund
7942   * Available under the MIT license
7943   * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
7944   */
7945
7946  var parseHeaders = function parseHeaders(headers) {
7947    var result = {};
7948
7949    if (!headers) {
7950      return result;
7951    }
7952
7953    headers.trim().split('\n').forEach(function (row) {
7954      var index = row.indexOf(':');
7955      var key = row.slice(0, index).trim().toLowerCase();
7956      var value = row.slice(index + 1).trim();
7957
7958      if (typeof result[key] === 'undefined') {
7959        result[key] = value;
7960      } else if (Array.isArray(result[key])) {
7961        result[key].push(value);
7962      } else {
7963        result[key] = [result[key], value];
7964      }
7965    });
vendor: 4,360 bytes, lines 7966-8132
7966    return result;
7967  };
7968
7969  var lib = createXHR; // Allow use of default import syntax in TypeScript
7970
7971  var default_1 = createXHR;
7972  createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop$1;
7973  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
7974  forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
7975    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7976      options = initParams(uri, options, callback);
7977      options.method = method.toUpperCase();
7978      return _createXHR(options);
7979    };
7980  });
7981
7982  function forEachArray(array, iterator) {
7983    for (var i = 0; i < array.length; i++) {
7984      iterator(array[i]);
7985    }
7986  }
7987
7988  function isEmpty(obj) {
7989    for (var i in obj) {
7990      if (obj.hasOwnProperty(i)) return false;
7991    }
7992
7993    return true;
7994  }
7995
7996  function initParams(uri, options, callback) {
7997    var params = uri;
7998
7999    if (isFunction_1(options)) {
8000      callback = options;
8001
8002      if (typeof uri === "string") {
8003        params = {
8004          uri: uri
8005        };
8006      }
8007    } else {
8008      params = _extends_1({}, options, {
8009        uri: uri
8010      });
8011    }
8012
8013    params.callback = callback;
8014    return params;
8015  }
8016
8017  function createXHR(uri, options, callback) {
8018    options = initParams(uri, options, callback);
8019    return _createXHR(options);
8020  }
8021
8022  function _createXHR(options) {
8023    if (typeof options.callback === "undefined") {
8024      throw new Error("callback argument missing");
8025    }
8026
8027    var called = false;
8028
8029    var callback = function cbOnce(err, response, body) {
8030      if (!called) {
8031        called = true;
8032        options.callback(err, response, body);
8033      }
8034    };
8035
8036    function readystatechange() {
8037      if (xhr.readyState === 4) {
8038        setTimeout(loadFunc, 0);
8039      }
8040    }
8041
8042    function getBody() {
8043      // Chrome with requestType=blob throws errors arround when even testing access to responseText
8044      var body = undefined;
8045
8046      if (xhr.response) {
8047        body = xhr.response;
8048      } else {
8049        body = xhr.responseText || getXml(xhr);
8050      }
8051
8052      if (isJson) {
8053        try {
8054          body = JSON.parse(body);
8055        } catch (e) {}
8056      }
8057
8058      return body;
8059    }
8060
8061    function errorFunc(evt) {
8062      clearTimeout(timeoutTimer);
8063
8064      if (!(evt instanceof Error)) {
8065        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
8066      }
8067
8068      evt.statusCode = 0;
8069      return callback(evt, failureResponse);
8070    } // will load the data & process the response in a special response object
8071
8072
8073    function loadFunc() {
8074      if (aborted) return;
8075      var status;
8076      clearTimeout(timeoutTimer);
8077
8078      if (options.useXDR && xhr.status === undefined) {
8079        //IE8 CORS GET successful response doesn't have a status field, but body is fine
8080        status = 200;
8081      } else {
8082        status = xhr.status === 1223 ? 204 : xhr.status;
8083      }
8084
8085      var response = failureResponse;
8086      var err = null;
8087
8088      if (status !== 0) {
8089        response = {
8090          body: getBody(),
8091          statusCode: status,
8092          method: method,
8093          headers: {},
8094          url: uri,
8095          rawRequest: xhr
8096        };
8097
8098        if (xhr.getAllResponseHeaders) {
8099          //remember xhr can in fact be XDR for CORS in IE
8100          response.headers = parseHeaders(xhr.getAllResponseHeaders());
8101        }
8102      } else {
8103        err = new Error("Internal XMLHttpRequest Error");
8104      }
8105
8106      return callback(err, response, response.body);
8107    }
8108
8109    var xhr = options.xhr || null;
8110
8111    if (!xhr) {
8112      if (options.cors || options.useXDR) {
8113        xhr = new createXHR.XDomainRequest();
8114      } else {
8115        xhr = new createXHR.XMLHttpRequest();
8116      }
8117    }
8118
8119    var key;
8120    var aborted;
8121    var uri = xhr.url = options.uri || options.url;
8122    var method = xhr.method = options.method || "GET";
8123    var body = options.body || options.data;
8124    var headers = xhr.headers = options.headers || {};
8125    var sync = !!options.sync;
8126    var isJson = false;
8127    var timeoutTimer;
8128    var failureResponse = {
8129      body: undefined,
8130      headers: {},
8131      statusCode: 0,
8132      method: method,
vendor: 4,119 bytes, lines 8133-8264
8133      url: uri,
8134      rawRequest: xhr
8135    };
8136
8137    if ("json" in options && options.json !== false) {
8138      isJson = true;
8139      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
8140
8141      if (method !== "GET" && method !== "HEAD") {
8142        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
8143
8144        body = JSON.stringify(options.json === true ? body : options.json);
8145      }
8146    }
8147
8148    xhr.onreadystatechange = readystatechange;
8149    xhr.onload = loadFunc;
8150    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
8151
8152    xhr.onprogress = function () {// IE must die
8153    };
8154
8155    xhr.onabort = function () {
8156      aborted = true;
8157    };
8158
8159    xhr.ontimeout = errorFunc;
8160    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
8161
8162    if (!sync) {
8163      xhr.withCredentials = !!options.withCredentials;
8164    } // Cannot set timeout with sync request
8165    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
8166    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
8167
8168
8169    if (!sync && options.timeout > 0) {
8170      timeoutTimer = setTimeout(function () {
8171        if (aborted) return;
8172        aborted = true; //IE9 may still call readystatechange
8173
8174        xhr.abort("timeout");
8175        var e = new Error("XMLHttpRequest timeout");
8176        e.code = "ETIMEDOUT";
8177        errorFunc(e);
8178      }, options.timeout);
8179    }
8180
8181    if (xhr.setRequestHeader) {
8182      for (key in headers) {
8183        if (headers.hasOwnProperty(key)) {
8184          xhr.setRequestHeader(key, headers[key]);
8185        }
8186      }
8187    } else if (options.headers && !isEmpty(options.headers)) {
8188      throw new Error("Headers cannot be set on an XDomainRequest object");
8189    }
8190
8191    if ("responseType" in options) {
8192      xhr.responseType = options.responseType;
8193    }
8194
8195    if ("beforeSend" in options && typeof options.beforeSend === "function") {
8196      options.beforeSend(xhr);
8197    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
8198    // XMLHttpRequest spec says to pass null as body to indicate no body
8199    // See https://github.com/naugtur/xhr/issues/100.
8200
8201
8202    xhr.send(body || null);
8203    return xhr;
8204  }
8205
8206  function getXml(xhr) {
8207    // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
8208    // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
8209    try {
8210      if (xhr.responseType === "document") {
8211        return xhr.responseXML;
8212      }
8213
8214      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
8215
8216      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
8217        return xhr.responseXML;
8218      }
8219    } catch (e) {}
8220
8221    return null;
8222  }
8223
8224  function noop$1() {}
8225  lib["default"] = default_1;
8226
8227  /**
8228   * Takes a webvtt file contents and parses it into cues
8229   *
8230   * @param {string} srcContent
8231   *        webVTT file contents
8232   *
8233   * @param {TextTrack} track
8234   *        TextTrack to add cues to. Cues come from the srcContent.
8235   *
8236   * @private
8237   */
8238
8239  var parseCues = function parseCues(srcContent, track) {
8240    var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder());
8241    var errors = [];
8242
8243    parser.oncue = function (cue) {
8244      track.addCue(cue);
8245    };
8246
8247    parser.onparsingerror = function (error) {
8248      errors.push(error);
8249    };
8250
8251    parser.onflush = function () {
8252      track.trigger({
8253        type: 'loadeddata',
8254        target: track
8255      });
8256    };
8257
8258    parser.parse(srcContent);
8259
8260    if (errors.length > 0) {
8261      if (window_1.console && window_1.console.groupCollapsed) {
8262        window_1.console.groupCollapsed("Text Track parsing errors for " + track.src);
8263      }
8264
8265      errors.forEach(function (error) {
8266        return log$1.error(error);
8267      });
8268
8269      if (window_1.console && window_1.console.groupEnd) {
8270        window_1.console.groupEnd();
8271      }
8272    }
8273
8274    parser.flush();
8275  };
8276  /**
8277   * Load a `TextTrack` from a specified url.
8278   *
8279   * @param {string} src
8280   *        Url to load track from.
8281   *
8282   * @param {TextTrack} track
8283   *        Track to add cues to. Comes from the content at the end of `url`.
8284   *
8285   * @private
8286   */
8287
8288
8289  var loadTrack = function loadTrack(src, track) {
8290    var opts = {
8291      uri: src
8292    };
8293    var crossOrigin = isCrossOrigin(src);
8294
8295    if (crossOrigin) {
8296      opts.cors = crossOrigin;
8297    }
8298
8299    var withCredentials = track.tech_.crossOrigin() === 'use-credentials';
8300
8301    if (withCredentials) {
8302      opts.withCredentials = withCredentials;
8303    }
8304
8305    lib(opts, bind(this, function (err, response, responseBody) {
8306      if (err) {
8307        return log$1.error(err, response);
8308      }
8309
8310      track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
8311      // NOTE: this is only used for the alt/video.novtt.js build
8312
8313      if (typeof window_1.WebVTT !== 'function') {
8314        if (track.tech_) {
8315          // to prevent use before define eslint error, we define loadHandler
8316          // as a let here
8317          track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) {
8318            if (event.type === 'vttjserror') {
8319              log$1.error("vttjs failed to load, stopping trying to process " + track.src);
8320              return;
8321            }
8322
8323            return parseCues(responseBody, track);
8324          });
8325        }
8326      } else {
8327        parseCues(responseBody, track);
8328      }
8329    }));
8330  };
8331  /**
8332   * A representation of a single `TextTrack`.
8333   *
8334   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
8335   * @extends Track
8336   */
8337
8338
8339  var TextTrack = /*#__PURE__*/function (_Track) {
8340    inheritsLoose(TextTrack, _Track);
8341
8342    /**
8343     * Create an instance of this class.
8344     *
8345     * @param {Object} options={}
8346     *        Object of option names and values
8347     *
8348     * @param {Tech} options.tech
8349     *        A reference to the tech that owns this TextTrack.
8350     *
8351     * @param {TextTrack~Kind} [options.kind='subtitles']
8352     *        A valid text track kind.
8353     *
8354     * @param {TextTrack~Mode} [options.mode='disabled']
8355     *        A valid text track mode.
8356     *
8357     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8358     *        A unique id for this TextTrack.
8359     *
8360     * @param {string} [options.label='']
8361     *        The menu label for this track.
8362     *
8363     * @param {string} [options.language='']
8364     *        A valid two character language code.
8365     *
8366     * @param {string} [options.srclang='']
8367     *        A valid two character language code. An alternative, but deprioritized
8368     *        version of `options.language`
8369     *
8370     * @param {string} [options.src]
8371     *        A url to TextTrack cues.
8372     *
8373     * @param {boolean} [options.default]
8374     *        If this track should default to on or off.
8375     */
8376    function TextTrack(options) {
8377      var _this;
8378
8379      if (options === void 0) {
8380        options = {};
8381      }
8382
8383      if (!options.tech) {
8384        throw new Error('A tech was not provided.');
8385      }
8386
8387      var settings = mergeOptions$3(options, {
8388        kind: TextTrackKind[options.kind] || 'subtitles',
8389        language: options.language || options.srclang || ''
8390      });
8391      var mode = TextTrackMode[settings.mode] || 'disabled';
8392      var default_ = settings["default"];
8393
8394      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
8395        mode = 'hidden';
8396      }
8397
8398      _this = _Track.call(this, settings) || this;
8399      _this.tech_ = settings.tech;
8400      _this.cues_ = [];
8401      _this.activeCues_ = [];
8402      _this.preload_ = _this.tech_.preloadTextTracks !== false;
8403      var cues = new TextTrackCueList(_this.cues_);
8404      var activeCues = new TextTrackCueList(_this.activeCues_);
8405      var changed = false;
8406      var timeupdateHandler = bind(assertThisInitialized(_this), function () {
8407        if (!this.tech_.isReady_ || this.tech_.isDisposed()) {
8408          return;
8409        } // Accessing this.activeCues for the side-effects of updating itself
8410        // due to its nature as a getter function. Do not remove or cues will
8411        // stop updating!
8412        // Use the setter to prevent deletion from uglify (pure_getters rule)
8413
8414
8415        this.activeCues = this.activeCues;
8416
8417        if (changed) {
8418          this.trigger('cuechange');
8419          changed = false;
8420        }
8421      });
8422
8423      var disposeHandler = function disposeHandler() {
8424        _this.tech_.off('timeupdate', timeupdateHandler);
8425      };
8426
8427      _this.tech_.one('dispose', disposeHandler);
8428
8429      if (mode !== 'disabled') {
8430        _this.tech_.on('timeupdate', timeupdateHandler);
8431      }
8432
8433      Object.defineProperties(assertThisInitialized(_this), {
8434        /**
8435         * @memberof TextTrack
8436         * @member {boolean} default
8437         *         If this track was set to be on or off by default. Cannot be changed after
8438         *         creation.
8439         * @instance
8440         *
8441         * @readonly
8442         */
8443        "default": {
8444          get: function get() {
8445            return default_;
8446          },
8447          set: function set() {}
8448        },
8449
8450        /**
8451         * @memberof TextTrack
8452         * @member {string} mode
8453         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
8454         *         not be set if setting to an invalid mode.
8455         * @instance
8456         *
8457         * @fires TextTrack#modechange
8458         */
8459        mode: {
8460          get: function get() {
8461            return mode;
8462          },
8463          set: function set(newMode) {
8464            if (!TextTrackMode[newMode]) {
8465              return;
8466            }
8467
8468            if (mode === newMode) {
8469              return;
8470            }
8471
8472            mode = newMode;
8473
8474            if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
8475              // On-demand load.
8476              loadTrack(this.src, this);
8477            }
8478
8479            this.tech_.off('timeupdate', timeupdateHandler);
8480
8481            if (mode !== 'disabled') {
8482              this.tech_.on('timeupdate', timeupdateHandler);
8483            }
8484            /**
8485             * An event that fires when mode changes on this track. This allows
8486             * the TextTrackList that holds this track to act accordingly.
8487             *
8488             * > Note: This is not part of the spec!
8489             *
8490             * @event TextTrack#modechange
8491             * @type {EventTarget~Event}
8492             */
8493
8494
8495            this.trigger('modechange');
8496          }
8497        },
8498
8499        /**
8500         * @memberof TextTrack
8501         * @member {TextTrackCueList} cues
8502         *         The text track cue list for this TextTrack.
8503         * @instance
8504         */
8505        cues: {
8506          get: function get() {
8507            if (!this.loaded_) {
8508              return null;
8509            }
8510
8511            return cues;
8512          },
8513          set: function set() {}
8514        },
8515
8516        /**
8517         * @memberof TextTrack
8518         * @member {TextTrackCueList} activeCues
8519         *         The list text track cues that are currently active for this TextTrack.
8520         * @instance
8521         */
8522        activeCues: {
8523          get: function get() {
8524            if (!this.loaded_) {
8525              return null;
8526            } // nothing to do
8527
8528
8529            if (this.cues.length === 0) {
8530              return activeCues;
8531            }
8532
8533            var ct = this.tech_.currentTime();
8534            var active = [];
8535
8536            for (var i = 0, l = this.cues.length; i < l; i++) {
8537              var cue = this.cues[i];
8538
8539              if (cue.startTime <= ct && cue.endTime >= ct) {
8540                active.push(cue);
8541              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
8542                active.push(cue);
8543              }
8544            }
8545
8546            changed = false;
8547
8548            if (active.length !== this.activeCues_.length) {
8549              changed = true;
8550            } else {
8551              for (var _i = 0; _i < active.length; _i++) {
8552                if (this.activeCues_.indexOf(active[_i]) === -1) {
8553                  changed = true;
8554                }
8555              }
8556            }
8557
8558            this.activeCues_ = active;
8559            activeCues.setCues_(this.activeCues_);
8560            return activeCues;
8561          },
8562          // /!\ Keep this setter empty (see the timeupdate handler above)
8563          set: function set() {}
8564        }
8565      });
8566
8567      if (settings.src) {
8568        _this.src = settings.src;
8569
8570        if (!_this.preload_) {
8571          // Tracks will load on-demand.
8572          // Act like we're loaded for other purposes.
8573          _this.loaded_ = true;
8574        }
8575
8576        if (_this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
8577          loadTrack(_this.src, assertThisInitialized(_this));
8578        }
8579      } else {
8580        _this.loaded_ = true;
8581      }
8582
8583      return _this;
8584    }
8585    /**
8586     * Add a cue to the internal list of cues.
8587     *
8588     * @param {TextTrack~Cue} cue
8589     *        The cue to add to our internal list
8590     */
8591
8592
8593    var _proto = TextTrack.prototype;
8594
8595    _proto.addCue = function addCue(originalCue) {
8596      var cue = originalCue;
8597
8598      if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) {
8599        cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8600
8601        for (var prop in originalCue) {
8602          if (!(prop in cue)) {
8603            cue[prop] = originalCue[prop];
8604          }
8605        } // make sure that `id` is copied over
8606
8607
8608        cue.id = originalCue.id;
8609        cue.originalCue_ = originalCue;
8610      }
8611
8612      var tracks = this.tech_.textTracks();
8613
8614      for (var i = 0; i < tracks.length; i++) {
8615        if (tracks[i] !== this) {
8616          tracks[i].removeCue(cue);
8617        }
8618      }
8619
8620      this.cues_.push(cue);
8621      this.cues.setCues_(this.cues_);
8622    }
8623    /**
8624     * Remove a cue from our internal list
8625     *
8626     * @param {TextTrack~Cue} removeCue
8627     *        The cue to remove from our internal list
8628     */
8629    ;
8630
8631    _proto.removeCue = function removeCue(_removeCue) {
8632      var i = this.cues_.length;
8633
8634      while (i--) {
8635        var cue = this.cues_[i];
8636
8637        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
8638          this.cues_.splice(i, 1);
8639          this.cues.setCues_(this.cues_);
8640          break;
8641        }
8642      }
8643    };
8644
8645    return TextTrack;
8646  }(Track);
8647  /**
8648   * cuechange - One or more cues in the track have become active or stopped being active.
8649   */
8650
8651
8652  TextTrack.prototype.allowedEvents_ = {
8653    cuechange: 'cuechange'
8654  };
8655
8656  /**
8657   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8658   * only one `AudioTrack` in the list will be enabled at a time.
8659   *
8660   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8661   * @extends Track
8662   */
8663
8664  var AudioTrack = /*#__PURE__*/function (_Track) {
8665    inheritsLoose(AudioTrack, _Track);
8666
8667    /**
8668     * Create an instance of this class.
8669     *
8670     * @param {Object} [options={}]
8671     *        Object of option names and values
8672     *
8673     * @param {AudioTrack~Kind} [options.kind='']
8674     *        A valid audio track kind
8675     *
8676     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8677     *        A unique id for this AudioTrack.
8678     *
8679     * @param {string} [options.label='']
8680     *        The menu label for this track.
8681     *
8682     * @param {string} [options.language='']
8683     *        A valid two character language code.
8684     *
8685     * @param {boolean} [options.enabled]
8686     *        If this track is the one that is currently playing. If this track is part of
8687     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8688     */
8689    function AudioTrack(options) {
8690      var _this;
8691
8692      if (options === void 0) {
8693        options = {};
8694      }
8695
8696      var settings = mergeOptions$3(options, {
8697        kind: AudioTrackKind[options.kind] || ''
8698      });
8699      _this = _Track.call(this, settings) || this;
8700      var enabled = false;
8701      /**
8702       * @memberof AudioTrack
8703       * @member {boolean} enabled
8704       *         If this `AudioTrack` is enabled or not. When setting this will
8705       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8706       * @instance
8707       *
8708       * @fires VideoTrack#selectedchange
8709       */
8710
8711      Object.defineProperty(assertThisInitialized(_this), 'enabled', {
8712        get: function get() {
8713          return enabled;
8714        },
8715        set: function set(newEnabled) {
8716          // an invalid or unchanged value
8717          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8718            return;
8719          }
8720
8721          enabled = newEnabled;
8722          /**
8723           * An event that fires when enabled changes on this track. This allows
8724           * the AudioTrackList that holds this track to act accordingly.
8725           *
8726           * > Note: This is not part of the spec! Native tracks will do
8727           *         this internally without an event.
8728           *
8729           * @event AudioTrack#enabledchange
8730           * @type {EventTarget~Event}
8731           */
8732
8733          this.trigger('enabledchange');
8734        }
8735      }); // if the user sets this track to selected then
8736      // set selected to that true value otherwise
8737      // we keep it false
8738
8739      if (settings.enabled) {
8740        _this.enabled = settings.enabled;
8741      }
8742
8743      _this.loaded_ = true;
8744      return _this;
8745    }
8746
8747    return AudioTrack;
8748  }(Track);
8749
8750  /**
8751   * A representation of a single `VideoTrack`.
8752   *
8753   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8754   * @extends Track
8755   */
8756
8757  var VideoTrack = /*#__PURE__*/function (_Track) {
8758    inheritsLoose(VideoTrack, _Track);
8759
8760    /**
8761     * Create an instance of this class.
8762     *
8763     * @param {Object} [options={}]
8764     *        Object of option names and values
8765     *
8766     * @param {string} [options.kind='']
8767     *        A valid {@link VideoTrack~Kind}
8768     *
8769     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8770     *        A unique id for this AudioTrack.
8771     *
8772     * @param {string} [options.label='']
8773     *        The menu label for this track.
8774     *
8775     * @param {string} [options.language='']
8776     *        A valid two character language code.
8777     *
8778     * @param {boolean} [options.selected]
8779     *        If this track is the one that is currently playing.
8780     */
8781    function VideoTrack(options) {
8782      var _this;
8783
8784      if (options === void 0) {
8785        options = {};
8786      }
8787
8788      var settings = mergeOptions$3(options, {
8789        kind: VideoTrackKind[options.kind] || ''
8790      });
8791      _this = _Track.call(this, settings) || this;
8792      var selected = false;
8793      /**
8794       * @memberof VideoTrack
8795       * @member {boolean} selected
8796       *         If this `VideoTrack` is selected or not. When setting this will
8797       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8798       * @instance
8799       *
8800       * @fires VideoTrack#selectedchange
8801       */
8802
8803      Object.defineProperty(assertThisInitialized(_this), 'selected', {
8804        get: function get() {
8805          return selected;
8806        },
8807        set: function set(newSelected) {
8808          // an invalid or unchanged value
8809          if (typeof newSelected !== 'boolean' || newSelected === selected) {
8810            return;
8811          }
8812
8813          selected = newSelected;
8814          /**
8815           * An event that fires when selected changes on this track. This allows
8816           * the VideoTrackList that holds this track to act accordingly.
8817           *
8818           * > Note: This is not part of the spec! Native tracks will do
8819           *         this internally without an event.
8820           *
8821           * @event VideoTrack#selectedchange
8822           * @type {EventTarget~Event}
8823           */
8824
8825          this.trigger('selectedchange');
8826        }
8827      }); // if the user sets this track to selected then
8828      // set selected to that true value otherwise
8829      // we keep it false
8830
8831      if (settings.selected) {
8832        _this.selected = settings.selected;
8833      }
8834
8835      return _this;
8836    }
8837
8838    return VideoTrack;
8839  }(Track);
8840
8841  /**
8842   * @memberof HTMLTrackElement
8843   * @typedef {HTMLTrackElement~ReadyState}
8844   * @enum {number}
8845   */
8846
8847  var NONE = 0;
8848  var LOADING = 1;
8849  var LOADED = 2;
8850  var ERROR = 3;
8851  /**
8852   * A single track represented in the DOM.
8853   *
8854   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8855   * @extends EventTarget
8856   */
8857
8858  var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) {
8859    inheritsLoose(HTMLTrackElement, _EventTarget);
8860
8861    /**
8862     * Create an instance of this class.
8863     *
8864     * @param {Object} options={}
8865     *        Object of option names and values
8866     *
8867     * @param {Tech} options.tech
8868     *        A reference to the tech that owns this HTMLTrackElement.
8869     *
8870     * @param {TextTrack~Kind} [options.kind='subtitles']
8871     *        A valid text track kind.
8872     *
8873     * @param {TextTrack~Mode} [options.mode='disabled']
8874     *        A valid text track mode.
8875     *
8876     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8877     *        A unique id for this TextTrack.
8878     *
8879     * @param {string} [options.label='']
8880     *        The menu label for this track.
8881     *
8882     * @param {string} [options.language='']
8883     *        A valid two character language code.
8884     *
8885     * @param {string} [options.srclang='']
8886     *        A valid two character language code. An alternative, but deprioritized
8887     *        vesion of `options.language`
8888     *
8889     * @param {string} [options.src]
8890     *        A url to TextTrack cues.
8891     *
8892     * @param {boolean} [options.default]
8893     *        If this track should default to on or off.
8894     */
8895    function HTMLTrackElement(options) {
8896      var _this;
8897
8898      if (options === void 0) {
8899        options = {};
8900      }
8901
8902      _this = _EventTarget.call(this) || this;
8903      var readyState;
8904      var track = new TextTrack(options);
8905      _this.kind = track.kind;
8906      _this.src = track.src;
8907      _this.srclang = track.language;
8908      _this.label = track.label;
8909      _this["default"] = track["default"];
8910      Object.defineProperties(assertThisInitialized(_this), {
8911        /**
8912         * @memberof HTMLTrackElement
8913         * @member {HTMLTrackElement~ReadyState} readyState
8914         *         The current ready state of the track element.
8915         * @instance
8916         */
8917        readyState: {
8918          get: function get() {
8919            return readyState;
8920          }
8921        },
8922
8923        /**
8924         * @memberof HTMLTrackElement
8925         * @member {TextTrack} track
8926         *         The underlying TextTrack object.
8927         * @instance
8928         *
8929         */
8930        track: {
8931          get: function get() {
8932            return track;
8933          }
8934        }
8935      });
8936      readyState = NONE;
8937      /**
8938       * @listens TextTrack#loadeddata
8939       * @fires HTMLTrackElement#load
8940       */
8941
8942      track.addEventListener('loadeddata', function () {
8943        readyState = LOADED;
8944
8945        _this.trigger({
8946          type: 'load',
8947          target: assertThisInitialized(_this)
8948        });
8949      });
8950      return _this;
8951    }
8952
8953    return HTMLTrackElement;
8954  }(EventTarget$2);
8955
8956  HTMLTrackElement.prototype.allowedEvents_ = {
8957    load: 'load'
8958  };
8959  HTMLTrackElement.NONE = NONE;
8960  HTMLTrackElement.LOADING = LOADING;
8961  HTMLTrackElement.LOADED = LOADED;
8962  HTMLTrackElement.ERROR = ERROR;
8963
8964  /*
8965   * This file contains all track properties that are used in
8966   * player.js, tech.js, html5.js and possibly other techs in the future.
8967   */
8968
8969  var NORMAL = {
8970    audio: {
8971      ListClass: AudioTrackList,
8972      TrackClass: AudioTrack,
8973      capitalName: 'Audio'
8974    },
8975    video: {
8976      ListClass: VideoTrackList,
8977      TrackClass: VideoTrack,
8978      capitalName: 'Video'
8979    },
8980    text: {
8981      ListClass: TextTrackList,
8982      TrackClass: TextTrack,
8983      capitalName: 'Text'
8984    }
8985  };
8986  Object.keys(NORMAL).forEach(function (type) {
8987    NORMAL[type].getterName = type + "Tracks";
8988    NORMAL[type].privateName = type + "Tracks_";
8989  });
8990  var REMOTE = {
8991    remoteText: {
8992      ListClass: TextTrackList,
8993      TrackClass: TextTrack,
8994      capitalName: 'RemoteText',
8995      getterName: 'remoteTextTracks',
8996      privateName: 'remoteTextTracks_'
8997    },
8998    remoteTextEl: {
8999      ListClass: HtmlTrackElementList,
9000      TrackClass: HTMLTrackElement,
9001      capitalName: 'RemoteTextTrackEls',
9002      getterName: 'remoteTextTrackEls',
9003      privateName: 'remoteTextTrackEls_'
9004    }
9005  };
9006
9007  var ALL = _extends_1({}, NORMAL, REMOTE);
9008
9009  REMOTE.names = Object.keys(REMOTE);
9010  NORMAL.names = Object.keys(NORMAL);
9011  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
9012
9013  /**
9014   * Copyright 2013 vtt.js Contributors
9015   *
9016   * Licensed under the Apache License, Version 2.0 (the "License");
9017   * you may not use this file except in compliance with the License.
9018   * You may obtain a copy of the License at
9019   *
9020   *   http://www.apache.org/licenses/LICENSE-2.0
9021   *
9022   * Unless required by applicable law or agreed to in writing, software
9023   * distributed under the License is distributed on an "AS IS" BASIS,
9024   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9025   * See the License for the specific language governing permissions and
9026   * limitations under the License.
9027   */
9028
9029  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
9030
9031  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
9032
9033  var _objCreate = Object.create || function () {
9034    function F() {}
9035
9036    return function (o) {
9037      if (arguments.length !== 1) {
9038        throw new Error('Object.create shim only accepts one parameter.');
9039      }
9040
9041      F.prototype = o;
9042      return new F();
9043    };
9044  }(); // Creates a new ParserError object from an errorData object. The errorData
9045  // object should have default code and message properties. The default message
9046  // property can be overriden by passing in a message parameter.
9047  // See ParsingError.Errors below for acceptable errors.
9048
9049
9050  function ParsingError(errorData, message) {
9051    this.name = "ParsingError";
9052    this.code = errorData.code;
9053    this.message = message || errorData.message;
9054  }
9055
9056  ParsingError.prototype = _objCreate(Error.prototype);
9057  ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
9058
9059  ParsingError.Errors = {
9060    BadSignature: {
9061      code: 0,
9062      message: "Malformed WebVTT signature."
9063    },
9064    BadTimeStamp: {
9065      code: 1,
9066      message: "Malformed time stamp."
9067    }
9068  }; // Try to parse input as a time stamp.
9069
9070  function parseTimeStamp(input) {
9071    function computeSeconds(h, m, s, f) {
9072      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
9073    }
9074
9075    var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
9076
9077    if (!m) {
9078      return null;
9079    }
9080
9081    if (m[3]) {
9082      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
9083      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
9084    } else if (m[1] > 59) {
9085      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
9086      // First position is hours as it's over 59.
9087      return computeSeconds(m[1], m[2], 0, m[4]);
9088    } else {
9089      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
9090      return computeSeconds(0, m[1], m[2], m[4]);
9091    }
9092  } // A settings object holds key/value pairs and will ignore anything but the first
9093  // assignment to a specific key.
9094
9095
9096  function Settings() {
9097    this.values = _objCreate(null);
9098  }
9099
9100  Settings.prototype = {
9101    // Only accept the first assignment to any key.
9102    set: function set(k, v) {
9103      if (!this.get(k) && v !== "") {
9104        this.values[k] = v;
9105      }
9106    },
9107    // Return the value for a key, or a default value.
9108    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
9109    // a number of possible default values as properties where 'defaultKey' is
9110    // the key of the property that will be chosen; otherwise it's assumed to be
9111    // a single value.
9112    get: function get(k, dflt, defaultKey) {
9113      if (defaultKey) {
9114        return this.has(k) ? this.values[k] : dflt[defaultKey];
9115      }
9116
9117      return this.has(k) ? this.values[k] : dflt;
9118    },
9119    // Check whether we have a value for a key.
9120    has: function has(k) {
9121      return k in this.values;
9122    },
9123    // Accept a setting if its one of the given alternatives.
9124    alt: function alt(k, v, a) {
9125      for (var n = 0; n < a.length; ++n) {
9126        if (v === a[n]) {
9127          this.set(k, v);
9128          break;
9129        }
9130      }
9131    },
9132    // Accept a setting if its a valid (signed) integer.
9133    integer: function integer(k, v) {
9134      if (/^-?\d+$/.test(v)) {
9135        // integer
9136        this.set(k, parseInt(v, 10));
9137      }
9138    },
9139    // Accept a setting if its a valid percentage.
9140    percent: function percent(k, v) {
9141
9142      if (v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
9143        v = parseFloat(v);
9144
9145        if (v >= 0 && v <= 100) {
9146          this.set(k, v);
9147          return true;
9148        }
9149      }
9150
9151      return false;
9152    }
9153  }; // Helper function to parse input into groups separated by 'groupDelim', and
9154  // interprete each group as a key/value pair separated by 'keyValueDelim'.
9155
9156  function parseOptions(input, callback, keyValueDelim, groupDelim) {
9157    var groups = groupDelim ? input.split(groupDelim) : [input];
9158
9159    for (var i in groups) {
9160      if (typeof groups[i] !== "string") {
9161        continue;
9162      }
9163
9164      var kv = groups[i].split(keyValueDelim);
9165
9166      if (kv.length !== 2) {
9167        continue;
9168      }
9169
9170      var k = kv[0];
9171      var v = kv[1];
9172      callback(k, v);
9173    }
9174  }
9175
9176  function parseCue(input, cue, regionList) {
9177    // Remember the original input if we need to throw an error.
9178    var oInput = input; // 4.1 WebVTT timestamp
9179
9180    function consumeTimeStamp() {
9181      var ts = parseTimeStamp(input);
9182
9183      if (ts === null) {
9184        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
9185      } // Remove time stamp from input.
9186
9187
9188      input = input.replace(/^[^\sa-zA-Z-]+/, "");
9189      return ts;
9190    } // 4.4.2 WebVTT cue settings
9191
9192
9193    function consumeCueSettings(input, cue) {
9194      var settings = new Settings();
vendor: 26,374 bytes, lines 9195-9949
9195      parseOptions(input, function (k, v) {
9196        switch (k) {
9197          case "region":
9198            // Find the last region we parsed with the same region id.
9199            for (var i = regionList.length - 1; i >= 0; i--) {
9200              if (regionList[i].id === v) {
9201                settings.set(k, regionList[i].region);
9202                break;
9203              }
9204            }
9205
9206            break;
9207
9208          case "vertical":
9209            settings.alt(k, v, ["rl", "lr"]);
9210            break;
9211
9212          case "line":
9213            var vals = v.split(","),
9214                vals0 = vals[0];
9215            settings.integer(k, vals0);
9216            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
9217            settings.alt(k, vals0, ["auto"]);
9218
9219            if (vals.length === 2) {
9220              settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
9221            }
9222
9223            break;
9224
9225          case "position":
9226            vals = v.split(",");
9227            settings.percent(k, vals[0]);
9228
9229            if (vals.length === 2) {
9230              settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
9231            }
9232
9233            break;
9234
9235          case "size":
9236            settings.percent(k, v);
9237            break;
9238
9239          case "align":
9240            settings.alt(k, v, ["start", "center", "end", "left", "right"]);
9241            break;
9242        }
9243      }, /:/, /\s/); // Apply default values for any missing fields.
9244
9245      cue.region = settings.get("region", null);
9246      cue.vertical = settings.get("vertical", "");
9247
9248      try {
9249        cue.line = settings.get("line", "auto");
9250      } catch (e) {}
9251
9252      cue.lineAlign = settings.get("lineAlign", "start");
9253      cue.snapToLines = settings.get("snapToLines", true);
9254      cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center
9255
9256      try {
9257        cue.align = settings.get("align", "center");
9258      } catch (e) {
9259        cue.align = settings.get("align", "middle");
9260      }
9261
9262      try {
9263        cue.position = settings.get("position", "auto");
9264      } catch (e) {
9265        cue.position = settings.get("position", {
9266          start: 0,
9267          left: 0,
9268          center: 50,
9269          middle: 50,
9270          end: 100,
9271          right: 100
9272        }, cue.align);
9273      }
9274
9275      cue.positionAlign = settings.get("positionAlign", {
9276        start: "start",
9277        left: "start",
9278        center: "center",
9279        middle: "center",
9280        end: "end",
9281        right: "end"
9282      }, cue.align);
9283    }
9284
9285    function skipWhitespace() {
9286      input = input.replace(/^\s+/, "");
9287    } // 4.1 WebVTT cue timings.
9288
9289
9290    skipWhitespace();
9291    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
9292
9293    skipWhitespace();
9294
9295    if (input.substr(0, 3) !== "-->") {
9296      // (3) next characters must match "-->"
9297      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
9298    }
9299
9300    input = input.substr(3);
9301    skipWhitespace();
9302    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
9303    // 4.1 WebVTT cue settings list.
9304
9305    skipWhitespace();
9306    consumeCueSettings(input, cue);
9307  } // When evaluating this file as part of a Webpack bundle for server
9308  // side rendering, `document` is an empty object.
9309
9310
9311  var TEXTAREA_ELEMENT = document_1.createElement && document_1.createElement("textarea");
9312  var TAG_NAME = {
9313    c: "span",
9314    i: "i",
9315    b: "b",
9316    u: "u",
9317    ruby: "ruby",
9318    rt: "rt",
9319    v: "span",
9320    lang: "span"
9321  }; // 5.1 default text color
9322  // 5.2 default text background color is equivalent to text color with bg_ prefix
9323
9324  var DEFAULT_COLOR_CLASS = {
9325    white: 'rgba(255,255,255,1)',
9326    lime: 'rgba(0,255,0,1)',
9327    cyan: 'rgba(0,255,255,1)',
9328    red: 'rgba(255,0,0,1)',
9329    yellow: 'rgba(255,255,0,1)',
9330    magenta: 'rgba(255,0,255,1)',
9331    blue: 'rgba(0,0,255,1)',
9332    black: 'rgba(0,0,0,1)'
9333  };
9334  var TAG_ANNOTATION = {
9335    v: "title",
9336    lang: "lang"
9337  };
9338  var NEEDS_PARENT = {
9339    rt: "ruby"
9340  }; // Parse content into a document fragment.
9341
9342  function parseContent(window, input) {
9343    function nextToken() {
9344      // Check for end-of-string.
9345      if (!input) {
9346        return null;
9347      } // Consume 'n' characters from the input.
9348
9349
9350      function consume(result) {
9351        input = input.substr(result.length);
9352        return result;
9353      }
9354
9355      var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
9356      // the tag.
9357
9358      return consume(m[1] ? m[1] : m[2]);
9359    }
9360
9361    function unescape(s) {
9362      TEXTAREA_ELEMENT.innerHTML = s;
9363      s = TEXTAREA_ELEMENT.textContent;
9364      TEXTAREA_ELEMENT.textContent = "";
9365      return s;
9366    }
9367
9368    function shouldAdd(current, element) {
9369      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
9370    } // Create an element for this tag.
9371
9372
9373    function createElement(type, annotation) {
9374      var tagName = TAG_NAME[type];
9375
9376      if (!tagName) {
9377        return null;
9378      }
9379
9380      var element = window.document.createElement(tagName);
9381      var name = TAG_ANNOTATION[type];
9382
9383      if (name && annotation) {
9384        element[name] = annotation.trim();
9385      }
9386
9387      return element;
9388    }
9389
9390    var rootDiv = window.document.createElement("div"),
9391        current = rootDiv,
9392        t,
9393        tagStack = [];
9394
9395    while ((t = nextToken()) !== null) {
9396      if (t[0] === '<') {
9397        if (t[1] === "/") {
9398          // If the closing tag matches, move back up to the parent node.
9399          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
9400            tagStack.pop();
9401            current = current.parentNode;
9402          } // Otherwise just ignore the end tag.
9403
9404
9405          continue;
9406        }
9407
9408        var ts = parseTimeStamp(t.substr(1, t.length - 2));
9409        var node;
9410
9411        if (ts) {
9412          // Timestamps are lead nodes as well.
9413          node = window.document.createProcessingInstruction("timestamp", ts);
9414          current.appendChild(node);
9415          continue;
9416        }
9417
9418        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
9419
9420        if (!m) {
9421          continue;
9422        } // Try to construct an element, and ignore the tag if we couldn't.
9423
9424
9425        node = createElement(m[1], m[3]);
9426
9427        if (!node) {
9428          continue;
9429        } // Determine if the tag should be added based on the context of where it
9430        // is placed in the cuetext.
9431
9432
9433        if (!shouldAdd(current, node)) {
9434          continue;
9435        } // Set the class list (as a list of classes, separated by space).
9436
9437
9438        if (m[2]) {
9439          var classes = m[2].split('.');
9440          classes.forEach(function (cl) {
9441            var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color
9442
9443            var colorName = bgColor ? cl.slice(3) : cl;
9444
9445            if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
9446              var propName = bgColor ? 'background-color' : 'color';
9447              var propValue = DEFAULT_COLOR_CLASS[colorName];
9448              node.style[propName] = propValue;
9449            }
9450          });
9451          node.className = classes.join(' ');
9452        } // Append the node to the current node, and enter the scope of the new
9453        // node.
9454
9455
9456        tagStack.push(m[1]);
9457        current.appendChild(node);
9458        current = node;
9459        continue;
9460      } // Text nodes are leaf nodes.
9461
9462
9463      current.appendChild(window.document.createTextNode(unescape(t)));
9464    }
9465
9466    return rootDiv;
9467  } // This is a list of all the Unicode characters that have a strong
9468  // right-to-left category. What this means is that these characters are
9469  // written right-to-left for sure. It was generated by pulling all the strong
9470  // right-to-left characters out of the Unicode data table. That table can
9471  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
9472
9473
9474  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]];
9475
9476  function isStrongRTLChar(charCode) {
9477    for (var i = 0; i < strongRTLRanges.length; i++) {
9478      var currentRange = strongRTLRanges[i];
9479
9480      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
9481        return true;
9482      }
9483    }
9484
9485    return false;
9486  }
9487
9488  function determineBidi(cueDiv) {
9489    var nodeStack = [],
9490        text = "",
9491        charCode;
9492
9493    if (!cueDiv || !cueDiv.childNodes) {
9494      return "ltr";
9495    }
9496
9497    function pushNodes(nodeStack, node) {
9498      for (var i = node.childNodes.length - 1; i >= 0; i--) {
9499        nodeStack.push(node.childNodes[i]);
9500      }
9501    }
9502
9503    function nextTextNode(nodeStack) {
9504      if (!nodeStack || !nodeStack.length) {
9505        return null;
9506      }
9507
9508      var node = nodeStack.pop(),
9509          text = node.textContent || node.innerText;
9510
9511      if (text) {
9512        // TODO: This should match all unicode type B characters (paragraph
9513        // separator characters). See issue #115.
9514        var m = text.match(/^.*(\n|\r)/);
9515
9516        if (m) {
9517          nodeStack.length = 0;
9518          return m[0];
9519        }
9520
9521        return text;
9522      }
9523
9524      if (node.tagName === "ruby") {
9525        return nextTextNode(nodeStack);
9526      }
9527
9528      if (node.childNodes) {
9529        pushNodes(nodeStack, node);
9530        return nextTextNode(nodeStack);
9531      }
9532    }
9533
9534    pushNodes(nodeStack, cueDiv);
9535
9536    while (text = nextTextNode(nodeStack)) {
9537      for (var i = 0; i < text.length; i++) {
9538        charCode = text.charCodeAt(i);
9539
9540        if (isStrongRTLChar(charCode)) {
9541          return "rtl";
9542        }
9543      }
9544    }
9545
9546    return "ltr";
9547  }
9548
9549  function computeLinePos(cue) {
9550    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
9551      return cue.line;
9552    }
9553
9554    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
9555      return -1;
9556    }
9557
9558    var track = cue.track,
9559        trackList = track.textTrackList,
9560        count = 0;
9561
9562    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
9563      if (trackList[i].mode === "showing") {
9564        count++;
9565      }
9566    }
9567
9568    return ++count * -1;
9569  }
9570
9571  function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
9572  // div on 'this'.
9573
9574
9575  StyleBox.prototype.applyStyles = function (styles, div) {
9576    div = div || this.div;
9577
9578    for (var prop in styles) {
9579      if (styles.hasOwnProperty(prop)) {
9580        div.style[prop] = styles[prop];
9581      }
9582    }
9583  };
9584
9585  StyleBox.prototype.formatStyle = function (val, unit) {
9586    return val === 0 ? 0 : val + unit;
9587  }; // Constructs the computed display state of the cue (a div). Places the div
9588  // into the overlay which should be a block level element (usually a div).
9589
9590
9591  function CueStyleBox(window, cue, styleOptions) {
9592    StyleBox.call(this);
9593    this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
9594    // have inline positioning and will function as the cue background box.
9595
9596    this.cueDiv = parseContent(window, cue.text);
9597    var styles = {
9598      color: "rgba(255, 255, 255, 1)",
9599      backgroundColor: "rgba(0, 0, 0, 0.8)",
9600      position: "relative",
9601      left: 0,
9602      right: 0,
9603      top: 0,
9604      bottom: 0,
9605      display: "inline",
9606      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9607      unicodeBidi: "plaintext"
9608    };
9609    this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
9610    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
9611    // mirrors of them except middle instead of center on Safari.
9612
9613    this.div = window.document.createElement("div");
9614    styles = {
9615      direction: determineBidi(this.cueDiv),
9616      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9617      unicodeBidi: "plaintext",
9618      textAlign: cue.align === "middle" ? "center" : cue.align,
9619      font: styleOptions.font,
9620      whiteSpace: "pre-line",
9621      position: "absolute"
9622    };
9623    this.applyStyles(styles);
9624    this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
9625    // position of the cue box. The reference edge will be resolved later when
9626    // the box orientation styles are applied.
9627
9628    var textPos = 0;
9629
9630    switch (cue.positionAlign) {
9631      case "start":
9632        textPos = cue.position;
9633        break;
9634
9635      case "center":
9636        textPos = cue.position - cue.size / 2;
9637        break;
9638
9639      case "end":
9640        textPos = cue.position - cue.size;
9641        break;
9642    } // Horizontal box orientation; textPos is the distance from the left edge of the
9643    // area to the left edge of the box and cue.size is the distance extending to
9644    // the right from there.
9645
9646
9647    if (cue.vertical === "") {
9648      this.applyStyles({
9649        left: this.formatStyle(textPos, "%"),
9650        width: this.formatStyle(cue.size, "%")
9651      }); // Vertical box orientation; textPos is the distance from the top edge of the
9652      // area to the top edge of the box and cue.size is the height extending
9653      // downwards from there.
9654    } else {
9655      this.applyStyles({
9656        top: this.formatStyle(textPos, "%"),
9657        height: this.formatStyle(cue.size, "%")
9658      });
9659    }
9660
9661    this.move = function (box) {
9662      this.applyStyles({
9663        top: this.formatStyle(box.top, "px"),
9664        bottom: this.formatStyle(box.bottom, "px"),
9665        left: this.formatStyle(box.left, "px"),
9666        right: this.formatStyle(box.right, "px"),
9667        height: this.formatStyle(box.height, "px"),
9668        width: this.formatStyle(box.width, "px")
9669      });
9670    };
9671  }
9672
9673  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9674  CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
9675  // compute things with such as if it overlaps or intersects with another Element.
9676  // Can initialize it with either a StyleBox or another BoxPosition.
9677
9678  function BoxPosition(obj) {
9679    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9680    // was passed in and we need to copy the results of 'getBoundingClientRect'
9681    // as the object returned is readonly. All co-ordinate values are in reference
9682    // to the viewport origin (top left).
9683    var lh, height, width, top;
9684
9685    if (obj.div) {
9686      height = obj.div.offsetHeight;
9687      width = obj.div.offsetWidth;
9688      top = obj.div.offsetTop;
9689      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9690      obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
9691      // the inner div's lines. This could be due to bold text, etc, on some platforms.
9692      // In this case we should get the average line height and use that. This will
9693      // result in the desired behaviour.
9694
9695      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9696    }
9697
9698    this.left = obj.left;
9699    this.right = obj.right;
9700    this.top = obj.top || top;
9701    this.height = obj.height || height;
9702    this.bottom = obj.bottom || top + (obj.height || height);
9703    this.width = obj.width || width;
9704    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9705  } // Move the box along a particular axis. Optionally pass in an amount to move
9706  // the box. If no amount is passed then the default is the line height of the
9707  // box.
9708
9709
9710  BoxPosition.prototype.move = function (axis, toMove) {
9711    toMove = toMove !== undefined ? toMove : this.lineHeight;
9712
9713    switch (axis) {
9714      case "+x":
9715        this.left += toMove;
9716        this.right += toMove;
9717        break;
9718
9719      case "-x":
9720        this.left -= toMove;
9721        this.right -= toMove;
9722        break;
9723
9724      case "+y":
9725        this.top += toMove;
9726        this.bottom += toMove;
9727        break;
9728
9729      case "-y":
9730        this.top -= toMove;
9731        this.bottom -= toMove;
9732        break;
9733    }
9734  }; // Check if this box overlaps another box, b2.
9735
9736
9737  BoxPosition.prototype.overlaps = function (b2) {
9738    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9739  }; // Check if this box overlaps any other boxes in boxes.
9740
9741
9742  BoxPosition.prototype.overlapsAny = function (boxes) {
9743    for (var i = 0; i < boxes.length; i++) {
9744      if (this.overlaps(boxes[i])) {
9745        return true;
9746      }
9747    }
9748
9749    return false;
9750  }; // Check if this box is within another box.
9751
9752
9753  BoxPosition.prototype.within = function (container) {
9754    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9755  }; // Check if this box is entirely within the container or it is overlapping
9756  // on the edge opposite of the axis direction passed. For example, if "+x" is
9757  // passed and the box is overlapping on the left edge of the container, then
9758  // return true.
9759
9760
9761  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9762    switch (axis) {
9763      case "+x":
9764        return this.left < container.left;
9765
9766      case "-x":
9767        return this.right > container.right;
9768
9769      case "+y":
9770        return this.top < container.top;
9771
9772      case "-y":
9773        return this.bottom > container.bottom;
9774    }
9775  }; // Find the percentage of the area that this box is overlapping with another
9776  // box.
9777
9778
9779  BoxPosition.prototype.intersectPercentage = function (b2) {
9780    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9781        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9782        intersectArea = x * y;
9783    return intersectArea / (this.height * this.width);
9784  }; // Convert the positions from this box to CSS compatible positions using
9785  // the reference container's positions. This has to be done because this
9786  // box's positions are in reference to the viewport origin, whereas, CSS
9787  // values are in referecne to their respective edges.
9788
9789
9790  BoxPosition.prototype.toCSSCompatValues = function (reference) {
9791    return {
9792      top: this.top - reference.top,
9793      bottom: reference.bottom - this.bottom,
9794      left: this.left - reference.left,
9795      right: reference.right - this.right,
9796      height: this.height,
9797      width: this.width
9798    };
9799  }; // Get an object that represents the box's position without anything extra.
9800  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9801
9802
9803  BoxPosition.getSimpleBoxPosition = function (obj) {
9804    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9805    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9806    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9807    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9808    var ret = {
9809      left: obj.left,
9810      right: obj.right,
9811      top: obj.top || top,
9812      height: obj.height || height,
9813      bottom: obj.bottom || top + (obj.height || height),
9814      width: obj.width || width
9815    };
9816    return ret;
9817  }; // Move a StyleBox to its specified, or next best, position. The containerBox
9818  // is the box that contains the StyleBox, such as a div. boxPositions are
9819  // a list of other boxes that the styleBox can't overlap with.
9820
9821
9822  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9823    // Find the best position for a cue box, b, on the video. The axis parameter
9824    // is a list of axis, the order of which, it will move the box along. For example:
9825    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9826    // direction. If it doesn't find a good position for it there it will then move
9827    // it along the x axis in the negative direction.
9828    function findBestPosition(b, axis) {
9829      var bestPosition,
9830          specifiedPosition = new BoxPosition(b),
9831          percentage = 1; // Highest possible so the first thing we get is better.
9832
9833      for (var i = 0; i < axis.length; i++) {
9834        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9835          b.move(axis[i]);
9836        } // We found a spot where we aren't overlapping anything. This is our
9837        // best position.
9838
9839
9840        if (b.within(containerBox)) {
9841          return b;
9842        }
9843
9844        var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
9845        // then remember this position as the best position.
9846
9847        if (percentage > p) {
9848          bestPosition = new BoxPosition(b);
9849          percentage = p;
9850        } // Reset the box position to the specified position.
9851
9852
9853        b = new BoxPosition(specifiedPosition);
9854      }
9855
9856      return bestPosition || specifiedPosition;
9857    }
9858
9859    var boxPosition = new BoxPosition(styleBox),
9860        cue = styleBox.cue,
9861        linePos = computeLinePos(cue),
9862        axis = []; // If we have a line number to align the cue to.
9863
9864    if (cue.snapToLines) {
9865      var size;
9866
9867      switch (cue.vertical) {
9868        case "":
9869          axis = ["+y", "-y"];
9870          size = "height";
9871          break;
9872
9873        case "rl":
9874          axis = ["+x", "-x"];
9875          size = "width";
9876          break;
9877
9878        case "lr":
9879          axis = ["-x", "+x"];
9880          size = "width";
9881          break;
9882      }
9883
9884      var step = boxPosition.lineHeight,
9885          position = step * Math.round(linePos),
9886          maxPosition = containerBox[size] + step,
9887          initialAxis = axis[0]; // If the specified intial position is greater then the max position then
9888      // clamp the box to the amount of steps it would take for the box to
9889      // reach the max position.
9890
9891      if (Math.abs(position) > maxPosition) {
9892        position = position < 0 ? -1 : 1;
9893        position *= Math.ceil(maxPosition / step) * step;
9894      } // If computed line position returns negative then line numbers are
9895      // relative to the bottom of the video instead of the top. Therefore, we
9896      // need to increase our initial position by the length or width of the
9897      // video, depending on the writing direction, and reverse our axis directions.
9898
9899
9900      if (linePos < 0) {
9901        position += cue.vertical === "" ? containerBox.height : containerBox.width;
9902        axis = axis.reverse();
9903      } // Move the box to the specified position. This may not be its best
9904      // position.
9905
9906
9907      boxPosition.move(initialAxis, position);
9908    } else {
9909      // If we have a percentage line value for the cue.
9910      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9911
9912      switch (cue.lineAlign) {
9913        case "center":
9914          linePos -= calculatedPercentage / 2;
9915          break;
9916
9917        case "end":
9918          linePos -= calculatedPercentage;
9919          break;
9920      } // Apply initial line position to the cue box.
9921
9922
9923      switch (cue.vertical) {
9924        case "":
9925          styleBox.applyStyles({
9926            top: styleBox.formatStyle(linePos, "%")
9927          });
9928          break;
9929
9930        case "rl":
9931          styleBox.applyStyles({
9932            left: styleBox.formatStyle(linePos, "%")
9933          });
9934          break;
9935
9936        case "lr":
9937          styleBox.applyStyles({
9938            right: styleBox.formatStyle(linePos, "%")
9939          });
9940          break;
9941      }
9942
9943      axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
9944      // to it.
9945
9946      boxPosition = new BoxPosition(styleBox);
9947    }
9948
9949    var bestPosition = findBestPosition(boxPosition, axis);
vendor: 5,111 bytes, lines 9950-10116
9950    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9951  }
9952
9953  function WebVTT$1() {// Nothing
9954  } // Helper to allow strings to be decoded instead of the default binary utf8 data.
9955
9956
9957  WebVTT$1.StringDecoder = function () {
9958    return {
9959      decode: function decode(data) {
9960        if (!data) {
9961          return "";
9962        }
9963
9964        if (typeof data !== "string") {
9965          throw new Error("Error - expected string data.");
9966        }
9967
9968        return decodeURIComponent(encodeURIComponent(data));
9969      }
9970    };
9971  };
9972
9973  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9974    if (!window || !cuetext) {
9975      return null;
9976    }
9977
9978    return parseContent(window, cuetext);
9979  };
9980
9981  var FONT_SIZE_PERCENT = 0.05;
9982  var FONT_STYLE = "sans-serif";
9983  var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
9984  // @param overlay A block level element (usually a div) that the computed cues
9985  //                and regions will be placed into.
9986
9987  WebVTT$1.processCues = function (window, cues, overlay) {
9988    if (!window || !cues || !overlay) {
9989      return null;
9990    } // Remove all previous children.
9991
9992
9993    while (overlay.firstChild) {
9994      overlay.removeChild(overlay.firstChild);
9995    }
9996
9997    var paddedOverlay = window.document.createElement("div");
9998    paddedOverlay.style.position = "absolute";
9999    paddedOverlay.style.left = "0";
10000    paddedOverlay.style.right = "0";
10001    paddedOverlay.style.top = "0";
10002    paddedOverlay.style.bottom = "0";
10003    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
10004    overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
10005    // be the case if a cue's state has been changed since the last computation or
10006    // if it has not been computed yet.
10007
10008    function shouldCompute(cues) {
10009      for (var i = 0; i < cues.length; i++) {
10010        if (cues[i].hasBeenReset || !cues[i].displayState) {
10011          return true;
10012        }
10013      }
10014
10015      return false;
10016    } // We don't need to recompute the cues' display states. Just reuse them.
10017
10018
10019    if (!shouldCompute(cues)) {
10020      for (var i = 0; i < cues.length; i++) {
10021        paddedOverlay.appendChild(cues[i].displayState);
10022      }
10023
10024      return;
10025    }
10026
10027    var boxPositions = [],
10028        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
10029        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
10030    var styleOptions = {
10031      font: fontSize + "px " + FONT_STYLE
10032    };
10033
10034    (function () {
10035      var styleBox, cue;
10036
10037      for (var i = 0; i < cues.length; i++) {
10038        cue = cues[i]; // Compute the intial position and styles of the cue div.
10039
10040        styleBox = new CueStyleBox(window, cue, styleOptions);
10041        paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
10042
10043        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
10044        // if we don't have too.
10045
10046        cue.displayState = styleBox.div;
10047        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
10048      }
10049    })();
10050  };
10051
10052  WebVTT$1.Parser = function (window, vttjs, decoder) {
10053    if (!decoder) {
10054      decoder = vttjs;
10055      vttjs = {};
10056    }
10057
10058    if (!vttjs) {
10059      vttjs = {};
10060    }
10061
10062    this.window = window;
10063    this.vttjs = vttjs;
10064    this.state = "INITIAL";
10065    this.buffer = "";
10066    this.decoder = decoder || new TextDecoder("utf8");
10067    this.regionList = [];
10068  };
10069
10070  WebVTT$1.Parser.prototype = {
10071    // If the error is a ParsingError then report it to the consumer if
10072    // possible. If it's not a ParsingError then throw it like normal.
10073    reportOrThrowError: function reportOrThrowError(e) {
10074      if (e instanceof ParsingError) {
10075        this.onparsingerror && this.onparsingerror(e);
10076      } else {
10077        throw e;
10078      }
10079    },
10080    parse: function parse(data) {
10081      var self = this; // If there is no data then we won't decode it, but will just try to parse
10082      // whatever is in buffer already. This may occur in circumstances, for
10083      // example when flush() is called.
10084
10085      if (data) {
10086        // Try to decode the data that we received.
10087        self.buffer += self.decoder.decode(data, {
10088          stream: true
10089        });
10090      }
10091
10092      function collectNextLine() {
10093        var buffer = self.buffer;
10094        var pos = 0;
10095
10096        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
10097          ++pos;
10098        }
10099
10100        var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
10101
10102        if (buffer[pos] === '\r') {
10103          ++pos;
10104        }
10105
10106        if (buffer[pos] === '\n') {
10107          ++pos;
10108        }
10109
10110        self.buffer = buffer.substr(pos);
10111        return line;
10112      } // 3.4 WebVTT region and WebVTT region settings syntax
10113
10114
10115      function parseRegion(input) {
10116        var settings = new Settings();
vendor: 8,124 bytes, lines 10117-10373
10117        parseOptions(input, function (k, v) {
10118          switch (k) {
10119            case "id":
10120              settings.set(k, v);
10121              break;
10122
10123            case "width":
10124              settings.percent(k, v);
10125              break;
10126
10127            case "lines":
10128              settings.integer(k, v);
10129              break;
10130
10131            case "regionanchor":
10132            case "viewportanchor":
10133              var xy = v.split(',');
10134
10135              if (xy.length !== 2) {
10136                break;
10137              } // We have to make sure both x and y parse, so use a temporary
10138              // settings object here.
10139
10140
10141              var anchor = new Settings();
10142              anchor.percent("x", xy[0]);
10143              anchor.percent("y", xy[1]);
10144
10145              if (!anchor.has("x") || !anchor.has("y")) {
10146                break;
10147              }
10148
10149              settings.set(k + "X", anchor.get("x"));
10150              settings.set(k + "Y", anchor.get("y"));
10151              break;
10152
10153            case "scroll":
10154              settings.alt(k, v, ["up"]);
10155              break;
10156          }
10157        }, /=/, /\s/); // Create the region, using default values for any values that were not
10158        // specified.
10159
10160        if (settings.has("id")) {
10161          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
10162          region.width = settings.get("width", 100);
10163          region.lines = settings.get("lines", 3);
10164          region.regionAnchorX = settings.get("regionanchorX", 0);
10165          region.regionAnchorY = settings.get("regionanchorY", 100);
10166          region.viewportAnchorX = settings.get("viewportanchorX", 0);
10167          region.viewportAnchorY = settings.get("viewportanchorY", 100);
10168          region.scroll = settings.get("scroll", ""); // Register the region.
10169
10170          self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
10171          // reference it.
10172
10173          self.regionList.push({
10174            id: settings.get("id"),
10175            region: region
10176          });
10177        }
10178      } // draft-pantos-http-live-streaming-20
10179      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
10180      // 3.5 WebVTT
10181
10182
10183      function parseTimestampMap(input) {
10184        var settings = new Settings();
10185        parseOptions(input, function (k, v) {
10186          switch (k) {
10187            case "MPEGT":
10188              settings.integer(k + 'S', v);
10189              break;
10190
10191            case "LOCA":
10192              settings.set(k + 'L', parseTimeStamp(v));
10193              break;
10194          }
10195        }, /[^\d]:/, /,/);
10196        self.ontimestampmap && self.ontimestampmap({
10197          "MPEGTS": settings.get("MPEGTS"),
10198          "LOCAL": settings.get("LOCAL")
10199        });
10200      } // 3.2 WebVTT metadata header syntax
10201
10202
10203      function parseHeader(input) {
10204        if (input.match(/X-TIMESTAMP-MAP/)) {
10205          // This line contains HLS X-TIMESTAMP-MAP metadata
10206          parseOptions(input, function (k, v) {
10207            switch (k) {
10208              case "X-TIMESTAMP-MAP":
10209                parseTimestampMap(v);
10210                break;
10211            }
10212          }, /=/);
10213        } else {
10214          parseOptions(input, function (k, v) {
10215            switch (k) {
10216              case "Region":
10217                // 3.3 WebVTT region metadata header syntax
10218                parseRegion(v);
10219                break;
10220            }
10221          }, /:/);
10222        }
10223      } // 5.1 WebVTT file parsing.
10224
10225
10226      try {
10227        var line;
10228
10229        if (self.state === "INITIAL") {
10230          // We can't start parsing until we have the first line.
10231          if (!/\r\n|\n/.test(self.buffer)) {
10232            return this;
10233          }
10234
10235          line = collectNextLine();
10236          var m = line.match(/^WEBVTT([ \t].*)?$/);
10237
10238          if (!m || !m[0]) {
10239            throw new ParsingError(ParsingError.Errors.BadSignature);
10240          }
10241
10242          self.state = "HEADER";
10243        }
10244
10245        var alreadyCollectedLine = false;
10246
10247        while (self.buffer) {
10248          // We can't parse a line until we have the full line.
10249          if (!/\r\n|\n/.test(self.buffer)) {
10250            return this;
10251          }
10252
10253          if (!alreadyCollectedLine) {
10254            line = collectNextLine();
10255          } else {
10256            alreadyCollectedLine = false;
10257          }
10258
10259          switch (self.state) {
10260            case "HEADER":
10261              // 13-18 - Allow a header (metadata) under the WEBVTT line.
10262              if (/:/.test(line)) {
10263                parseHeader(line);
10264              } else if (!line) {
10265                // An empty line terminates the header and starts the body (cues).
10266                self.state = "ID";
10267              }
10268
10269              continue;
10270
10271            case "NOTE":
10272              // Ignore NOTE blocks.
10273              if (!line) {
10274                self.state = "ID";
10275              }
10276
10277              continue;
10278
10279            case "ID":
10280              // Check for the start of NOTE blocks.
10281              if (/^NOTE($|[ \t])/.test(line)) {
10282                self.state = "NOTE";
10283                break;
10284              } // 19-29 - Allow any number of line terminators, then initialize new cue values.
10285
10286
10287              if (!line) {
10288                continue;
10289              }
10290
10291              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center
10292
10293              try {
10294                self.cue.align = "center";
10295              } catch (e) {
10296                self.cue.align = "middle";
10297              }
10298
10299              self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
10300
10301              if (line.indexOf("-->") === -1) {
10302                self.cue.id = line;
10303                continue;
10304              }
10305
10306            // Process line as start of a cue.
10307
10308            /*falls through*/
10309
10310            case "CUE":
10311              // 40 - Collect cue timings and settings.
10312              try {
10313                parseCue(line, self.cue, self.regionList);
10314              } catch (e) {
10315                self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
10316
10317                self.cue = null;
10318                self.state = "BADCUE";
10319                continue;
10320              }
10321
10322              self.state = "CUETEXT";
10323              continue;
10324
10325            case "CUETEXT":
10326              var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
10327              // 35 - If we have the special substring '-->' then report the cue,
10328              // but do not collect the line as we need to process the current
10329              // one as a new cue.
10330
10331              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
10332                // We are done parsing self cue.
10333                self.oncue && self.oncue(self.cue);
10334                self.cue = null;
10335                self.state = "ID";
10336                continue;
10337              }
10338
10339              if (self.cue.text) {
10340                self.cue.text += "\n";
10341              }
10342
10343              self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
10344              continue;
10345
10346            case "BADCUE":
10347              // BADCUE
10348              // 54-62 - Collect and discard the remaining cue.
10349              if (!line) {
10350                self.state = "ID";
10351              }
10352
10353              continue;
10354          }
10355        }
10356      } catch (e) {
10357        self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
10358
10359        if (self.state === "CUETEXT" && self.cue && self.oncue) {
10360          self.oncue(self.cue);
10361        }
10362
10363        self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
10364        // another exception occurred so enter BADCUE state.
10365
10366        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
10367      }
10368
10369      return this;
10370    },
10371    flush: function flush() {
10372      var self = this;
10373
10374      try {
10375        // Finish decoding the stream.
10376        self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
10377
10378        if (self.cue || self.state === "HEADER") {
10379          self.buffer += "\n\n";
10380          self.parse();
10381        } // If we've flushed, parsed, and we're still on the INITIAL state then
10382        // that means we don't have enough of the stream to parse the first
10383        // line.
10384
10385
10386        if (self.state === "INITIAL") {
10387          throw new ParsingError(ParsingError.Errors.BadSignature);
10388        }
10389      } catch (e) {
10390        self.reportOrThrowError(e);
10391      }
10392
10393      self.onflush && self.onflush();
10394      return this;
10395    }
10396  };
10397  var vtt = WebVTT$1;
10398
10399  /**
10400   * Copyright 2013 vtt.js Contributors
10401   *
10402   * Licensed under the Apache License, Version 2.0 (the "License");
10403   * you may not use this file except in compliance with the License.
10404   * You may obtain a copy of the License at
10405   *
10406   *   http://www.apache.org/licenses/LICENSE-2.0
10407   *
10408   * Unless required by applicable law or agreed to in writing, software
10409   * distributed under the License is distributed on an "AS IS" BASIS,
10410   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10411   * See the License for the specific language governing permissions and
10412   * limitations under the License.
10413   */
10414  var autoKeyword = "auto";
10415  var directionSetting = {
10416    "": 1,
10417    "lr": 1,
10418    "rl": 1
10419  };
10420  var alignSetting = {
10421    "start": 1,
10422    "center": 1,
10423    "end": 1,
10424    "left": 1,
10425    "right": 1,
10426    "auto": 1,
10427    "line-left": 1,
10428    "line-right": 1
10429  };
10430
10431  function findDirectionSetting(value) {
10432    if (typeof value !== "string") {
10433      return false;
10434    }
10435
10436    var dir = directionSetting[value.toLowerCase()];
10437    return dir ? value.toLowerCase() : false;
10438  }
10439
10440  function findAlignSetting(value) {
10441    if (typeof value !== "string") {
10442      return false;
10443    }
10444
10445    var align = alignSetting[value.toLowerCase()];
10446    return align ? value.toLowerCase() : false;
10447  }
10448
10449  function VTTCue(startTime, endTime, text) {
10450    /**
10451     * Shim implementation specific properties. These properties are not in
10452     * the spec.
10453     */
10454    // Lets us know when the VTTCue's data has changed in such a way that we need
10455    // to recompute its display state. This lets us compute its display state
10456    // lazily.
10457    this.hasBeenReset = false;
10458    /**
10459     * VTTCue and TextTrackCue properties
10460     * http://dev.w3.org/html5/webvtt/#vttcue-interface
10461     */
10462
10463    var _id = "";
10464    var _pauseOnExit = false;
10465    var _startTime = startTime;
10466    var _endTime = endTime;
10467    var _text = text;
10468    var _region = null;
10469    var _vertical = "";
10470    var _snapToLines = true;
10471    var _line = "auto";
10472    var _lineAlign = "start";
10473    var _position = "auto";
10474    var _positionAlign = "auto";
10475    var _size = 100;
10476    var _align = "center";
10477    Object.defineProperties(this, {
10478      "id": {
10479        enumerable: true,
10480        get: function get() {
10481          return _id;
10482        },
10483        set: function set(value) {
10484          _id = "" + value;
10485        }
10486      },
10487      "pauseOnExit": {
10488        enumerable: true,
10489        get: function get() {
10490          return _pauseOnExit;
10491        },
10492        set: function set(value) {
10493          _pauseOnExit = !!value;
10494        }
10495      },
10496      "startTime": {
10497        enumerable: true,
10498        get: function get() {
10499          return _startTime;
10500        },
10501        set: function set(value) {
10502          if (typeof value !== "number") {
10503            throw new TypeError("Start time must be set to a number.");
10504          }
10505
10506          _startTime = value;
10507          this.hasBeenReset = true;
10508        }
10509      },
10510      "endTime": {
10511        enumerable: true,
10512        get: function get() {
10513          return _endTime;
10514        },
10515        set: function set(value) {
10516          if (typeof value !== "number") {
10517            throw new TypeError("End time must be set to a number.");
10518          }
10519
10520          _endTime = value;
10521          this.hasBeenReset = true;
10522        }
10523      },
10524      "text": {
10525        enumerable: true,
10526        get: function get() {
10527          return _text;
10528        },
10529        set: function set(value) {
10530          _text = "" + value;
10531          this.hasBeenReset = true;
10532        }
10533      },
10534      "region": {
10535        enumerable: true,
10536        get: function get() {
10537          return _region;
10538        },
10539        set: function set(value) {
10540          _region = value;
10541          this.hasBeenReset = true;
10542        }
10543      },
10544      "vertical": {
10545        enumerable: true,
10546        get: function get() {
10547          return _vertical;
10548        },
10549        set: function set(value) {
10550          var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
10551
10552          if (setting === false) {
10553            throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
10554          }
10555
10556          _vertical = setting;
10557          this.hasBeenReset = true;
10558        }
10559      },
10560      "snapToLines": {
10561        enumerable: true,
10562        get: function get() {
10563          return _snapToLines;
10564        },
10565        set: function set(value) {
10566          _snapToLines = !!value;
10567          this.hasBeenReset = true;
10568        }
10569      },
10570      "line": {
10571        enumerable: true,
10572        get: function get() {
10573          return _line;
10574        },
10575        set: function set(value) {
10576          if (typeof value !== "number" && value !== autoKeyword) {
10577            throw new SyntaxError("Line: an invalid number or illegal string was specified.");
10578          }
10579
10580          _line = value;
10581          this.hasBeenReset = true;
10582        }
10583      },
10584      "lineAlign": {
10585        enumerable: true,
10586        get: function get() {
10587          return _lineAlign;
10588        },
10589        set: function set(value) {
10590          var setting = findAlignSetting(value);
10591
10592          if (!setting) {
10593            console.warn("lineAlign: an invalid or illegal string was specified.");
10594          } else {
10595            _lineAlign = setting;
10596            this.hasBeenReset = true;
10597          }
10598        }
10599      },
10600      "position": {
10601        enumerable: true,
10602        get: function get() {
10603          return _position;
10604        },
10605        set: function set(value) {
10606          if (value < 0 || value > 100) {
10607            throw new Error("Position must be between 0 and 100.");
10608          }
10609
10610          _position = value;
10611          this.hasBeenReset = true;
10612        }
10613      },
10614      "positionAlign": {
10615        enumerable: true,
10616        get: function get() {
10617          return _positionAlign;
10618        },
10619        set: function set(value) {
10620          var setting = findAlignSetting(value);
10621
10622          if (!setting) {
10623            console.warn("positionAlign: an invalid or illegal string was specified.");
10624          } else {
10625            _positionAlign = setting;
10626            this.hasBeenReset = true;
10627          }
10628        }
10629      },
10630      "size": {
10631        enumerable: true,
10632        get: function get() {
10633          return _size;
10634        },
10635        set: function set(value) {
10636          if (value < 0 || value > 100) {
10637            throw new Error("Size must be between 0 and 100.");
10638          }
10639
10640          _size = value;
10641          this.hasBeenReset = true;
10642        }
10643      },
10644      "align": {
10645        enumerable: true,
10646        get: function get() {
10647          return _align;
10648        },
10649        set: function set(value) {
10650          var setting = findAlignSetting(value);
10651
10652          if (!setting) {
10653            throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
10654          }
10655
10656          _align = setting;
10657          this.hasBeenReset = true;
10658        }
10659      }
10660    });
10661    /**
10662     * Other <track> spec defined properties
10663     */
10664    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
10665
10666    this.displayState = undefined;
10667  }
10668  /**
10669   * VTTCue methods
10670   */
10671
10672
10673  VTTCue.prototype.getCueAsHTML = function () {
10674    // Assume WebVTT.convertCueToDOMTree is on the global.
10675    return WebVTT.convertCueToDOMTree(window, this.text);
10676  };
10677
10678  var vttcue = VTTCue;
10679
10680  /**
10681   * Copyright 2013 vtt.js Contributors
10682   *
10683   * Licensed under the Apache License, Version 2.0 (the "License");
10684   * you may not use this file except in compliance with the License.
10685   * You may obtain a copy of the License at
10686   *
10687   *   http://www.apache.org/licenses/LICENSE-2.0
10688   *
10689   * Unless required by applicable law or agreed to in writing, software
10690   * distributed under the License is distributed on an "AS IS" BASIS,
10691   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10692   * See the License for the specific language governing permissions and
10693   * limitations under the License.
10694   */
10695  var scrollSetting = {
10696    "": true,
10697    "up": true
10698  };
10699
10700  function findScrollSetting(value) {
10701    if (typeof value !== "string") {
10702      return false;
10703    }
10704
10705    var scroll = scrollSetting[value.toLowerCase()];
10706    return scroll ? value.toLowerCase() : false;
10707  }
10708
10709  function isValidPercentValue(value) {
10710    return typeof value === "number" && value >= 0 && value <= 100;
10711  } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
10712
10713
10714  function VTTRegion() {
10715    var _width = 100;
10716    var _lines = 3;
10717    var _regionAnchorX = 0;
10718    var _regionAnchorY = 100;
10719    var _viewportAnchorX = 0;
10720    var _viewportAnchorY = 100;
10721    var _scroll = "";
10722    Object.defineProperties(this, {
10723      "width": {
10724        enumerable: true,
10725        get: function get() {
10726          return _width;
10727        },
10728        set: function set(value) {
10729          if (!isValidPercentValue(value)) {
10730            throw new Error("Width must be between 0 and 100.");
10731          }
10732
10733          _width = value;
10734        }
10735      },
10736      "lines": {
10737        enumerable: true,
10738        get: function get() {
10739          return _lines;
10740        },
10741        set: function set(value) {
10742          if (typeof value !== "number") {
10743            throw new TypeError("Lines must be set to a number.");
10744          }
10745
10746          _lines = value;
10747        }
10748      },
10749      "regionAnchorY": {
10750        enumerable: true,
10751        get: function get() {
10752          return _regionAnchorY;
10753        },
10754        set: function set(value) {
10755          if (!isValidPercentValue(value)) {
10756            throw new Error("RegionAnchorX must be between 0 and 100.");
10757          }
10758
10759          _regionAnchorY = value;
10760        }
10761      },
10762      "regionAnchorX": {
10763        enumerable: true,
10764        get: function get() {
10765          return _regionAnchorX;
10766        },
10767        set: function set(value) {
10768          if (!isValidPercentValue(value)) {
10769            throw new Error("RegionAnchorY must be between 0 and 100.");
10770          }
10771
10772          _regionAnchorX = value;
10773        }
10774      },
10775      "viewportAnchorY": {
10776        enumerable: true,
10777        get: function get() {
10778          return _viewportAnchorY;
10779        },
10780        set: function set(value) {
10781          if (!isValidPercentValue(value)) {
10782            throw new Error("ViewportAnchorY must be between 0 and 100.");
10783          }
10784
10785          _viewportAnchorY = value;
10786        }
10787      },
10788      "viewportAnchorX": {
10789        enumerable: true,
10790        get: function get() {
10791          return _viewportAnchorX;
10792        },
10793        set: function set(value) {
10794          if (!isValidPercentValue(value)) {
10795            throw new Error("ViewportAnchorX must be between 0 and 100.");
10796          }
10797
10798          _viewportAnchorX = value;
10799        }
10800      },
10801      "scroll": {
10802        enumerable: true,
10803        get: function get() {
10804          return _scroll;
10805        },
10806        set: function set(value) {
10807          var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
10808
10809          if (setting === false) {
10810            console.warn("Scroll: an invalid or illegal string was specified.");
10811          } else {
10812            _scroll = setting;
10813          }
10814        }
10815      }
10816    });
10817  }
10818
10819  var vttregion = VTTRegion;
10820
10821  var browserIndex = createCommonjsModule(function (module) {
10822    /**
10823     * Copyright 2013 vtt.js Contributors
10824     *
10825     * Licensed under the Apache License, Version 2.0 (the "License");
10826     * you may not use this file except in compliance with the License.
10827     * You may obtain a copy of the License at
10828     *
10829     *   http://www.apache.org/licenses/LICENSE-2.0
10830     *
10831     * Unless required by applicable law or agreed to in writing, software
10832     * distributed under the License is distributed on an "AS IS" BASIS,
10833     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10834     * See the License for the specific language governing permissions and
10835     * limitations under the License.
10836     */
10837    // Default exports for Node. Export the extended versions of VTTCue and
10838    // VTTRegion in Node since we likely want the capability to convert back and
10839    // forth between JSON. If we don't then it's not that big of a deal since we're
10840    // off browser.
10841    var vttjs = module.exports = {
10842      WebVTT: vtt,
10843      VTTCue: vttcue,
10844      VTTRegion: vttregion
10845    };
10846    window_1.vttjs = vttjs;
10847    window_1.WebVTT = vttjs.WebVTT;
10848    var cueShim = vttjs.VTTCue;
10849    var regionShim = vttjs.VTTRegion;
10850    var nativeVTTCue = window_1.VTTCue;
10851    var nativeVTTRegion = window_1.VTTRegion;
10852
10853    vttjs.shim = function () {
10854      window_1.VTTCue = cueShim;
10855      window_1.VTTRegion = regionShim;
10856    };
10857
10858    vttjs.restore = function () {
10859      window_1.VTTCue = nativeVTTCue;
10860      window_1.VTTRegion = nativeVTTRegion;
10861    };
10862
10863    if (!window_1.VTTCue) {
10864      vttjs.shim();
10865    }
10866  });
10867  browserIndex.WebVTT;
10868  browserIndex.VTTCue;
10869  browserIndex.VTTRegion;
10870
10871  /**
10872   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10873   * that just contains the src url alone.
10874   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10875     * `var SourceString = 'http://example.com/some-video.mp4';`
10876   *
10877   * @typedef {Object|string} Tech~SourceObject
10878   *
10879   * @property {string} src
10880   *           The url to the source
10881   *
10882   * @property {string} type
10883   *           The mime type of the source
10884   */
10885
10886  /**
10887   * A function used by {@link Tech} to create a new {@link TextTrack}.
10888   *
10889   * @private
10890   *
10891   * @param {Tech} self
10892   *        An instance of the Tech class.
10893   *
10894   * @param {string} kind
10895   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10896   *
10897   * @param {string} [label]
10898   *        Label to identify the text track
10899   *
10900   * @param {string} [language]
10901   *        Two letter language abbreviation
10902   *
10903   * @param {Object} [options={}]
10904   *        An object with additional text track options
10905   *
10906   * @return {TextTrack}
10907   *          The text track that was created.
10908   */
10909
10910  function createTrackHelper(self, kind, label, language, options) {
10911    if (options === void 0) {
10912      options = {};
10913    }
10914
10915    var tracks = self.textTracks();
10916    options.kind = kind;
10917
10918    if (label) {
10919      options.label = label;
10920    }
10921
10922    if (language) {
10923      options.language = language;
10924    }
10925
10926    options.tech = self;
10927    var track = new ALL.text.TrackClass(options);
10928    tracks.addTrack(track);
10929    return track;
10930  }
10931  /**
10932   * This is the base class for media playback technology controllers, such as
10933   * {@link HTML5}
10934   *
10935   * @extends Component
10936   */
10937
10938
10939  var Tech = /*#__PURE__*/function (_Component) {
10940    inheritsLoose(Tech, _Component);
10941
10942    /**
10943    * Create an instance of this Tech.
10944    *
10945    * @param {Object} [options]
10946    *        The key/value store of player options.
10947    *
10948    * @param {Component~ReadyCallback} ready
10949    *        Callback function to call when the `HTML5` Tech is ready.
10950    */
10951    function Tech(options, ready) {
10952      var _this;
10953
10954      if (options === void 0) {
10955        options = {};
10956      }
10957
10958      if (ready === void 0) {
10959        ready = function ready() {};
10960      }
10961
10962      // we don't want the tech to report user activity automatically.
vendor: 10,830 bytes, lines 10963-11308
10963      // This is done manually in addControlsListeners
10964      options.reportTouchActivity = false;
10965      _this = _Component.call(this, null, options, ready) || this;
10966
10967      _this.onDurationChange_ = function (e) {
10968        return _this.onDurationChange(e);
10969      };
10970
10971      _this.trackProgress_ = function (e) {
10972        return _this.trackProgress(e);
10973      };
10974
10975      _this.trackCurrentTime_ = function (e) {
10976        return _this.trackCurrentTime(e);
10977      };
10978
10979      _this.stopTrackingCurrentTime_ = function (e) {
10980        return _this.stopTrackingCurrentTime(e);
10981      };
10982
10983      _this.disposeSourceHandler_ = function (e) {
10984        return _this.disposeSourceHandler(e);
10985      }; // keep track of whether the current source has played at all to
10986      // implement a very limited played()
10987
10988
10989      _this.hasStarted_ = false;
10990
10991      _this.on('playing', function () {
10992        this.hasStarted_ = true;
10993      });
10994
10995      _this.on('loadstart', function () {
10996        this.hasStarted_ = false;
10997      });
10998
10999      ALL.names.forEach(function (name) {
11000        var props = ALL[name];
11001
11002        if (options && options[props.getterName]) {
11003          _this[props.privateName] = options[props.getterName];
11004        }
11005      }); // Manually track progress in cases where the browser/tech doesn't report it.
11006
11007      if (!_this.featuresProgressEvents) {
11008        _this.manualProgressOn();
11009      } // Manually track timeupdates in cases where the browser/tech doesn't report it.
11010
11011
11012      if (!_this.featuresTimeupdateEvents) {
11013        _this.manualTimeUpdatesOn();
11014      }
11015
11016      ['Text', 'Audio', 'Video'].forEach(function (track) {
11017        if (options["native" + track + "Tracks"] === false) {
11018          _this["featuresNative" + track + "Tracks"] = false;
11019        }
11020      });
11021
11022      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
11023        _this.featuresNativeTextTracks = false;
11024      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
11025        _this.featuresNativeTextTracks = true;
11026      }
11027
11028      if (!_this.featuresNativeTextTracks) {
11029        _this.emulateTextTracks();
11030      }
11031
11032      _this.preloadTextTracks = options.preloadTextTracks !== false;
11033      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
11034
11035      _this.initTrackListeners(); // Turn on component tap events only if not using native controls
11036
11037
11038      if (!options.nativeControlsForTouch) {
11039        _this.emitTapEvents();
11040      }
11041
11042      if (_this.constructor) {
11043        _this.name_ = _this.constructor.name || 'Unknown Tech';
11044      }
11045
11046      return _this;
11047    }
11048    /**
11049     * A special function to trigger source set in a way that will allow player
11050     * to re-trigger if the player or tech are not ready yet.
11051     *
11052     * @fires Tech#sourceset
11053     * @param {string} src The source string at the time of the source changing.
11054     */
11055
11056
11057    var _proto = Tech.prototype;
11058
11059    _proto.triggerSourceset = function triggerSourceset(src) {
11060      var _this2 = this;
11061
11062      if (!this.isReady_) {
11063        // on initial ready we have to trigger source set
11064        // 1ms after ready so that player can watch for it.
11065        this.one('ready', function () {
11066          return _this2.setTimeout(function () {
11067            return _this2.triggerSourceset(src);
11068          }, 1);
11069        });
11070      }
11071      /**
11072       * Fired when the source is set on the tech causing the media element
11073       * to reload.
11074       *
11075       * @see {@link Player#event:sourceset}
11076       * @event Tech#sourceset
11077       * @type {EventTarget~Event}
11078       */
11079
11080
11081      this.trigger({
11082        src: src,
11083        type: 'sourceset'
11084      });
11085    }
11086    /* Fallbacks for unsupported event types
11087    ================================================================================ */
11088
11089    /**
11090     * Polyfill the `progress` event for browsers that don't support it natively.
11091     *
11092     * @see {@link Tech#trackProgress}
11093     */
11094    ;
11095
11096    _proto.manualProgressOn = function manualProgressOn() {
11097      this.on('durationchange', this.onDurationChange_);
11098      this.manualProgress = true; // Trigger progress watching when a source begins loading
11099
11100      this.one('ready', this.trackProgress_);
11101    }
11102    /**
11103     * Turn off the polyfill for `progress` events that was created in
11104     * {@link Tech#manualProgressOn}
11105     */
11106    ;
11107
11108    _proto.manualProgressOff = function manualProgressOff() {
11109      this.manualProgress = false;
11110      this.stopTrackingProgress();
11111      this.off('durationchange', this.onDurationChange_);
11112    }
11113    /**
11114     * This is used to trigger a `progress` event when the buffered percent changes. It
11115     * sets an interval function that will be called every 500 milliseconds to check if the
11116     * buffer end percent has changed.
11117     *
11118     * > This function is called by {@link Tech#manualProgressOn}
11119     *
11120     * @param {EventTarget~Event} event
11121     *        The `ready` event that caused this to run.
11122     *
11123     * @listens Tech#ready
11124     * @fires Tech#progress
11125     */
11126    ;
11127
11128    _proto.trackProgress = function trackProgress(event) {
11129      this.stopTrackingProgress();
11130      this.progressInterval = this.setInterval(bind(this, function () {
11131        // Don't trigger unless buffered amount is greater than last time
11132        var numBufferedPercent = this.bufferedPercent();
11133
11134        if (this.bufferedPercent_ !== numBufferedPercent) {
11135          /**
11136           * See {@link Player#progress}
11137           *
11138           * @event Tech#progress
11139           * @type {EventTarget~Event}
11140           */
11141          this.trigger('progress');
11142        }
11143
11144        this.bufferedPercent_ = numBufferedPercent;
11145
11146        if (numBufferedPercent === 1) {
11147          this.stopTrackingProgress();
11148        }
11149      }), 500);
11150    }
11151    /**
11152     * Update our internal duration on a `durationchange` event by calling
11153     * {@link Tech#duration}.
11154     *
11155     * @param {EventTarget~Event} event
11156     *        The `durationchange` event that caused this to run.
11157     *
11158     * @listens Tech#durationchange
11159     */
11160    ;
11161
11162    _proto.onDurationChange = function onDurationChange(event) {
11163      this.duration_ = this.duration();
11164    }
11165    /**
11166     * Get and create a `TimeRange` object for buffering.
11167     *
11168     * @return {TimeRange}
11169     *         The time range object that was created.
11170     */
11171    ;
11172
11173    _proto.buffered = function buffered() {
11174      return createTimeRanges(0, 0);
11175    }
11176    /**
11177     * Get the percentage of the current video that is currently buffered.
11178     *
11179     * @return {number}
11180     *         A number from 0 to 1 that represents the decimal percentage of the
11181     *         video that is buffered.
11182     *
11183     */
11184    ;
11185
11186    _proto.bufferedPercent = function bufferedPercent$1() {
11187      return bufferedPercent(this.buffered(), this.duration_);
11188    }
11189    /**
11190     * Turn off the polyfill for `progress` events that was created in
11191     * {@link Tech#manualProgressOn}
11192     * Stop manually tracking progress events by clearing the interval that was set in
11193     * {@link Tech#trackProgress}.
11194     */
11195    ;
11196
11197    _proto.stopTrackingProgress = function stopTrackingProgress() {
11198      this.clearInterval(this.progressInterval);
11199    }
11200    /**
11201     * Polyfill the `timeupdate` event for browsers that don't support it.
11202     *
11203     * @see {@link Tech#trackCurrentTime}
11204     */
11205    ;
11206
11207    _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
11208      this.manualTimeUpdates = true;
11209      this.on('play', this.trackCurrentTime_);
11210      this.on('pause', this.stopTrackingCurrentTime_);
11211    }
11212    /**
11213     * Turn off the polyfill for `timeupdate` events that was created in
11214     * {@link Tech#manualTimeUpdatesOn}
11215     */
11216    ;
11217
11218    _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
11219      this.manualTimeUpdates = false;
11220      this.stopTrackingCurrentTime();
11221      this.off('play', this.trackCurrentTime_);
11222      this.off('pause', this.stopTrackingCurrentTime_);
11223    }
11224    /**
11225     * Sets up an interval function to track current time and trigger `timeupdate` every
11226     * 250 milliseconds.
11227     *
11228     * @listens Tech#play
11229     * @triggers Tech#timeupdate
11230     */
11231    ;
11232
11233    _proto.trackCurrentTime = function trackCurrentTime() {
11234      if (this.currentTimeInterval) {
11235        this.stopTrackingCurrentTime();
11236      }
11237
11238      this.currentTimeInterval = this.setInterval(function () {
11239        /**
11240         * Triggered at an interval of 250ms to indicated that time is passing in the video.
11241         *
11242         * @event Tech#timeupdate
11243         * @type {EventTarget~Event}
11244         */
11245        this.trigger({
11246          type: 'timeupdate',
11247          target: this,
11248          manuallyTriggered: true
11249        }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
11250      }, 250);
11251    }
11252    /**
11253     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
11254     * `timeupdate` event is no longer triggered.
11255     *
11256     * @listens {Tech#pause}
11257     */
11258    ;
11259
11260    _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
11261      this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
11262      // the progress bar won't make it all the way to the end
11263
11264      this.trigger({
11265        type: 'timeupdate',
11266        target: this,
11267        manuallyTriggered: true
11268      });
11269    }
11270    /**
11271     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
11272     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
11273     *
11274     * @fires Component#dispose
11275     */
11276    ;
11277
11278    _proto.dispose = function dispose() {
11279      // clear out all tracks because we can't reuse them between techs
11280      this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
11281
11282      if (this.manualProgress) {
11283        this.manualProgressOff();
11284      }
11285
11286      if (this.manualTimeUpdates) {
11287        this.manualTimeUpdatesOff();
11288      }
11289
11290      _Component.prototype.dispose.call(this);
11291    }
11292    /**
11293     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
11294     *
11295     * > Note: Techs without source handlers should call this between sources for `video`
11296     *         & `audio` tracks. You don't want to use them between tracks!
11297     *
11298     * @param {string[]|string} types
11299     *        TrackList names to clear, valid names are `video`, `audio`, and
11300     *        `text`.
11301     */
11302    ;
11303
11304    _proto.clearTracks = function clearTracks(types) {
11305      var _this3 = this;
11306
11307      types = [].concat(types); // clear out all tracks because we can't reuse them between techs
11308
11309      types.forEach(function (type) {
11310        var list = _this3[type + "Tracks"]() || [];
11311        var i = list.length;
11312
11313        while (i--) {
11314          var track = list[i];
11315
11316          if (type === 'text') {
11317            _this3.removeRemoteTextTrack(track);
11318          }
11319
11320          list.removeTrack(track);
11321        }
11322      });
11323    }
11324    /**
11325     * Remove any TextTracks added via addRemoteTextTrack that are
11326     * flagged for automatic garbage collection
11327     */
11328    ;
11329
11330    _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
11331      var list = this.autoRemoteTextTracks_ || [];
11332      var i = list.length;
11333
11334      while (i--) {
11335        var track = list[i];
11336        this.removeRemoteTextTrack(track);
11337      }
11338    }
11339    /**
11340     * Reset the tech, which will removes all sources and reset the internal readyState.
11341     *
11342     * @abstract
11343     */
11344    ;
11345
11346    _proto.reset = function reset() {}
11347    /**
11348     * Get the value of `crossOrigin` from the tech.
11349     *
11350     * @abstract
11351     *
11352     * @see {Html5#crossOrigin}
11353     */
11354    ;
11355
11356    _proto.crossOrigin = function crossOrigin() {}
11357    /**
11358     * Set the value of `crossOrigin` on the tech.
11359     *
11360     * @abstract
11361     *
11362     * @param {string} crossOrigin the crossOrigin value
11363     * @see {Html5#setCrossOrigin}
11364     */
11365    ;
11366
11367    _proto.setCrossOrigin = function setCrossOrigin() {}
11368    /**
11369     * Get or set an error on the Tech.
11370     *
11371     * @param {MediaError} [err]
11372     *        Error to set on the Tech
11373     *
11374     * @return {MediaError|null}
11375     *         The current error object on the tech, or null if there isn't one.
11376     */
11377    ;
11378
11379    _proto.error = function error(err) {
11380      if (err !== undefined) {
11381        this.error_ = new MediaError(err);
11382        this.trigger('error');
11383      }
11384
11385      return this.error_;
11386    }
11387    /**
11388     * Returns the `TimeRange`s that have been played through for the current source.
11389     *
11390     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
11391     *         It only checks whether the source has played at all or not.
11392     *
11393     * @return {TimeRange}
11394     *         - A single time range if this video has played
11395     *         - An empty set of ranges if not.
11396     */
11397    ;
11398
11399    _proto.played = function played() {
11400      if (this.hasStarted_) {
11401        return createTimeRanges(0, 0);
11402      }
11403
11404      return createTimeRanges();
11405    }
11406    /**
11407     * Start playback
11408     *
11409     * @abstract
11410     *
11411     * @see {Html5#play}
11412     */
11413    ;
11414
11415    _proto.play = function play() {}
11416    /**
11417     * Set whether we are scrubbing or not
11418     *
11419     * @abstract
11420     *
11421     * @see {Html5#setScrubbing}
11422     */
11423    ;
11424
11425    _proto.setScrubbing = function setScrubbing() {}
11426    /**
11427     * Get whether we are scrubbing or not
11428     *
11429     * @abstract
11430     *
11431     * @see {Html5#scrubbing}
11432     */
11433    ;
11434
11435    _proto.scrubbing = function scrubbing() {}
11436    /**
11437     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
11438     * previously called.
11439     *
11440     * @fires Tech#timeupdate
11441     */
11442    ;
11443
11444    _proto.setCurrentTime = function setCurrentTime() {
11445      // improve the accuracy of manual timeupdates
11446      if (this.manualTimeUpdates) {
11447        /**
11448         * A manual `timeupdate` event.
11449         *
11450         * @event Tech#timeupdate
11451         * @type {EventTarget~Event}
11452         */
11453        this.trigger({
11454          type: 'timeupdate',
11455          target: this,
11456          manuallyTriggered: true
11457        });
11458      }
11459    }
11460    /**
11461     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
11462     * {@link TextTrackList} events.
11463     *
11464     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
11465     *
11466     * @fires Tech#audiotrackchange
11467     * @fires Tech#videotrackchange
11468     * @fires Tech#texttrackchange
11469     */
11470    ;
11471
11472    _proto.initTrackListeners = function initTrackListeners() {
11473      var _this4 = this;
11474
11475      /**
11476        * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
11477        *
11478        * @event Tech#audiotrackchange
11479        * @type {EventTarget~Event}
11480        */
11481
11482      /**
11483        * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
11484        *
11485        * @event Tech#videotrackchange
11486        * @type {EventTarget~Event}
11487        */
11488
11489      /**
11490        * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
11491        *
11492        * @event Tech#texttrackchange
11493        * @type {EventTarget~Event}
11494        */
vendor: 30,070 bytes, lines 11495-12501
11495      NORMAL.names.forEach(function (name) {
11496        var props = NORMAL[name];
11497
11498        var trackListChanges = function trackListChanges() {
11499          _this4.trigger(name + "trackchange");
11500        };
11501
11502        var tracks = _this4[props.getterName]();
11503
11504        tracks.addEventListener('removetrack', trackListChanges);
11505        tracks.addEventListener('addtrack', trackListChanges);
11506
11507        _this4.on('dispose', function () {
11508          tracks.removeEventListener('removetrack', trackListChanges);
11509          tracks.removeEventListener('addtrack', trackListChanges);
11510        });
11511      });
11512    }
11513    /**
11514     * Emulate TextTracks using vtt.js if necessary
11515     *
11516     * @fires Tech#vttjsloaded
11517     * @fires Tech#vttjserror
11518     */
11519    ;
11520
11521    _proto.addWebVttScript_ = function addWebVttScript_() {
11522      var _this5 = this;
11523
11524      if (window_1.WebVTT) {
11525        return;
11526      } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
11527      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
11528      // before inserting the WebVTT script
11529
11530
11531      if (document_1.body.contains(this.el())) {
11532        // load via require if available and vtt.js script location was not passed in
11533        // as an option. novtt builds will turn the above require call into an empty object
11534        // which will cause this if check to always fail.
11535        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
11536          this.trigger('vttjsloaded');
11537          return;
11538        } // load vtt.js via the script location option or the cdn of no location was
11539        // passed in
11540
11541
11542        var script = document_1.createElement('script');
11543        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
11544
11545        script.onload = function () {
11546          /**
11547           * Fired when vtt.js is loaded.
11548           *
11549           * @event Tech#vttjsloaded
11550           * @type {EventTarget~Event}
11551           */
11552          _this5.trigger('vttjsloaded');
11553        };
11554
11555        script.onerror = function () {
11556          /**
11557           * Fired when vtt.js was not loaded due to an error
11558           *
11559           * @event Tech#vttjsloaded
11560           * @type {EventTarget~Event}
11561           */
11562          _this5.trigger('vttjserror');
11563        };
11564
11565        this.on('dispose', function () {
11566          script.onload = null;
11567          script.onerror = null;
11568        }); // but have not loaded yet and we set it to true before the inject so that
11569        // we don't overwrite the injected window.WebVTT if it loads right away
11570
11571        window_1.WebVTT = true;
11572        this.el().parentNode.appendChild(script);
11573      } else {
11574        this.ready(this.addWebVttScript_);
11575      }
11576    }
11577    /**
11578     * Emulate texttracks
11579     *
11580     */
11581    ;
11582
11583    _proto.emulateTextTracks = function emulateTextTracks() {
11584      var _this6 = this;
11585
11586      var tracks = this.textTracks();
11587      var remoteTracks = this.remoteTextTracks();
11588
11589      var handleAddTrack = function handleAddTrack(e) {
11590        return tracks.addTrack(e.track);
11591      };
11592
11593      var handleRemoveTrack = function handleRemoveTrack(e) {
11594        return tracks.removeTrack(e.track);
11595      };
11596
11597      remoteTracks.on('addtrack', handleAddTrack);
11598      remoteTracks.on('removetrack', handleRemoveTrack);
11599      this.addWebVttScript_();
11600
11601      var updateDisplay = function updateDisplay() {
11602        return _this6.trigger('texttrackchange');
11603      };
11604
11605      var textTracksChanges = function textTracksChanges() {
11606        updateDisplay();
11607
11608        for (var i = 0; i < tracks.length; i++) {
11609          var track = tracks[i];
11610          track.removeEventListener('cuechange', updateDisplay);
11611
11612          if (track.mode === 'showing') {
11613            track.addEventListener('cuechange', updateDisplay);
11614          }
11615        }
11616      };
11617
11618      textTracksChanges();
11619      tracks.addEventListener('change', textTracksChanges);
11620      tracks.addEventListener('addtrack', textTracksChanges);
11621      tracks.addEventListener('removetrack', textTracksChanges);
11622      this.on('dispose', function () {
11623        remoteTracks.off('addtrack', handleAddTrack);
11624        remoteTracks.off('removetrack', handleRemoveTrack);
11625        tracks.removeEventListener('change', textTracksChanges);
11626        tracks.removeEventListener('addtrack', textTracksChanges);
11627        tracks.removeEventListener('removetrack', textTracksChanges);
11628
11629        for (var i = 0; i < tracks.length; i++) {
11630          var track = tracks[i];
11631          track.removeEventListener('cuechange', updateDisplay);
11632        }
11633      });
11634    }
11635    /**
11636     * Create and returns a remote {@link TextTrack} object.
11637     *
11638     * @param {string} kind
11639     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
11640     *
11641     * @param {string} [label]
11642     *        Label to identify the text track
11643     *
11644     * @param {string} [language]
11645     *        Two letter language abbreviation
11646     *
11647     * @return {TextTrack}
11648     *         The TextTrack that gets created.
11649     */
11650    ;
11651
11652    _proto.addTextTrack = function addTextTrack(kind, label, language) {
11653      if (!kind) {
11654        throw new Error('TextTrack kind is required but was not provided');
11655      }
11656
11657      return createTrackHelper(this, kind, label, language);
11658    }
11659    /**
11660     * Create an emulated TextTrack for use by addRemoteTextTrack
11661     *
11662     * This is intended to be overridden by classes that inherit from
11663     * Tech in order to create native or custom TextTracks.
11664     *
11665     * @param {Object} options
11666     *        The object should contain the options to initialize the TextTrack with.
11667     *
11668     * @param {string} [options.kind]
11669     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
11670     *
11671     * @param {string} [options.label].
11672     *        Label to identify the text track
11673     *
11674     * @param {string} [options.language]
11675     *        Two letter language abbreviation.
11676     *
11677     * @return {HTMLTrackElement}
11678     *         The track element that gets created.
11679     */
11680    ;
11681
11682    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
11683      var track = mergeOptions$3(options, {
11684        tech: this
11685      });
11686      return new REMOTE.remoteTextEl.TrackClass(track);
11687    }
11688    /**
11689     * Creates a remote text track object and returns an html track element.
11690     *
11691     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
11692     *
11693     * @param {Object} options
11694     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
11695     *
11696     * @param {boolean} [manualCleanup=true]
11697     *        - When false: the TextTrack will be automatically removed from the video
11698     *          element whenever the source changes
11699     *        - When True: The TextTrack will have to be cleaned up manually
11700     *
11701     * @return {HTMLTrackElement}
11702     *         An Html Track Element.
11703     *
11704     * @deprecated The default functionality for this function will be equivalent
11705     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
11706     *             also be removed.
11707     */
11708    ;
11709
11710    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
11711      var _this7 = this;
11712
11713      if (options === void 0) {
11714        options = {};
11715      }
11716
11717      var htmlTrackElement = this.createRemoteTextTrack(options);
11718
11719      if (manualCleanup !== true && manualCleanup !== false) {
11720        // deprecation warning
11721        log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
11722        manualCleanup = true;
11723      } // store HTMLTrackElement and TextTrack to remote list
11724
11725
11726      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
11727      this.remoteTextTracks().addTrack(htmlTrackElement.track);
11728
11729      if (manualCleanup !== true) {
11730        // create the TextTrackList if it doesn't exist
11731        this.ready(function () {
11732          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
11733        });
11734      }
11735
11736      return htmlTrackElement;
11737    }
11738    /**
11739     * Remove a remote text track from the remote `TextTrackList`.
11740     *
11741     * @param {TextTrack} track
11742     *        `TextTrack` to remove from the `TextTrackList`
11743     */
11744    ;
11745
11746    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
11747      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
11748
11749      this.remoteTextTrackEls().removeTrackElement_(trackElement);
11750      this.remoteTextTracks().removeTrack(track);
11751      this.autoRemoteTextTracks_.removeTrack(track);
11752    }
11753    /**
11754     * Gets available media playback quality metrics as specified by the W3C's Media
11755     * Playback Quality API.
11756     *
11757     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
11758     *
11759     * @return {Object}
11760     *         An object with supported media playback quality metrics
11761     *
11762     * @abstract
11763     */
11764    ;
11765
11766    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
11767      return {};
11768    }
11769    /**
11770     * Attempt to create a floating video window always on top of other windows
11771     * so that users may continue consuming media while they interact with other
11772     * content sites, or applications on their device.
11773     *
11774     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
11775     *
11776     * @return {Promise|undefined}
11777     *         A promise with a Picture-in-Picture window if the browser supports
11778     *         Promises (or one was passed in as an option). It returns undefined
11779     *         otherwise.
11780     *
11781     * @abstract
11782     */
11783    ;
11784
11785    _proto.requestPictureInPicture = function requestPictureInPicture() {
11786      var PromiseClass = this.options_.Promise || window_1.Promise;
11787
11788      if (PromiseClass) {
11789        return PromiseClass.reject();
11790      }
11791    }
11792    /**
11793     * A method to check for the value of the 'disablePictureInPicture' <video> property.
11794     * Defaults to true, as it should be considered disabled if the tech does not support pip
11795     *
11796     * @abstract
11797     */
11798    ;
11799
11800    _proto.disablePictureInPicture = function disablePictureInPicture() {
11801      return true;
11802    }
11803    /**
11804     * A method to set or unset the 'disablePictureInPicture' <video> property.
11805     *
11806     * @abstract
11807     */
11808    ;
11809
11810    _proto.setDisablePictureInPicture = function setDisablePictureInPicture() {}
11811    /**
11812     * A method to set a poster from a `Tech`.
11813     *
11814     * @abstract
11815     */
11816    ;
11817
11818    _proto.setPoster = function setPoster() {}
11819    /**
11820     * A method to check for the presence of the 'playsinline' <video> attribute.
11821     *
11822     * @abstract
11823     */
11824    ;
11825
11826    _proto.playsinline = function playsinline() {}
11827    /**
11828     * A method to set or unset the 'playsinline' <video> attribute.
11829     *
11830     * @abstract
11831     */
11832    ;
11833
11834    _proto.setPlaysinline = function setPlaysinline() {}
11835    /**
11836     * Attempt to force override of native audio tracks.
11837     *
11838     * @param {boolean} override - If set to true native audio will be overridden,
11839     * otherwise native audio will potentially be used.
11840     *
11841     * @abstract
11842     */
11843    ;
11844
11845    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
11846    /**
11847     * Attempt to force override of native video tracks.
11848     *
11849     * @param {boolean} override - If set to true native video will be overridden,
11850     * otherwise native video will potentially be used.
11851     *
11852     * @abstract
11853     */
11854    ;
11855
11856    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
11857    /*
11858     * Check if the tech can support the given mime-type.
11859     *
11860     * The base tech does not support any type, but source handlers might
11861     * overwrite this.
11862     *
11863     * @param  {string} type
11864     *         The mimetype to check for support
11865     *
11866     * @return {string}
11867     *         'probably', 'maybe', or empty string
11868     *
11869     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11870     *
11871     * @abstract
11872     */
11873    ;
11874
11875    _proto.canPlayType = function canPlayType() {
11876      return '';
11877    }
11878    /**
11879     * Check if the type is supported by this tech.
11880     *
11881     * The base tech does not support any type, but source handlers might
11882     * overwrite this.
11883     *
11884     * @param {string} type
11885     *        The media type to check
11886     * @return {string} Returns the native video element's response
11887     */
11888    ;
11889
11890    Tech.canPlayType = function canPlayType() {
11891      return '';
11892    }
11893    /**
11894     * Check if the tech can support the given source
11895     *
11896     * @param {Object} srcObj
11897     *        The source object
11898     * @param {Object} options
11899     *        The options passed to the tech
11900     * @return {string} 'probably', 'maybe', or '' (empty string)
11901     */
11902    ;
11903
11904    Tech.canPlaySource = function canPlaySource(srcObj, options) {
11905      return Tech.canPlayType(srcObj.type);
11906    }
11907    /*
11908     * Return whether the argument is a Tech or not.
11909     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11910     *
11911     * @param {Object} component
11912     *        The item to check
11913     *
11914     * @return {boolean}
11915     *         Whether it is a tech or not
11916     *         - True if it is a tech
11917     *         - False if it is not
11918     */
11919    ;
11920
11921    Tech.isTech = function isTech(component) {
11922      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11923    }
11924    /**
11925     * Registers a `Tech` into a shared list for videojs.
11926     *
11927     * @param {string} name
11928     *        Name of the `Tech` to register.
11929     *
11930     * @param {Object} tech
11931     *        The `Tech` class to register.
11932     */
11933    ;
11934
11935    Tech.registerTech = function registerTech(name, tech) {
11936      if (!Tech.techs_) {
11937        Tech.techs_ = {};
11938      }
11939
11940      if (!Tech.isTech(tech)) {
11941        throw new Error("Tech " + name + " must be a Tech");
11942      }
11943
11944      if (!Tech.canPlayType) {
11945        throw new Error('Techs must have a static canPlayType method on them');
11946      }
11947
11948      if (!Tech.canPlaySource) {
11949        throw new Error('Techs must have a static canPlaySource method on them');
11950      }
11951
11952      name = toTitleCase$1(name);
11953      Tech.techs_[name] = tech;
11954      Tech.techs_[toLowerCase(name)] = tech;
11955
11956      if (name !== 'Tech') {
11957        // camel case the techName for use in techOrder
11958        Tech.defaultTechOrder_.push(name);
11959      }
11960
11961      return tech;
11962    }
11963    /**
11964     * Get a `Tech` from the shared list by name.
11965     *
11966     * @param {string} name
11967     *        `camelCase` or `TitleCase` name of the Tech to get
11968     *
11969     * @return {Tech|undefined}
11970     *         The `Tech` or undefined if there was no tech with the name requested.
11971     */
11972    ;
11973
11974    Tech.getTech = function getTech(name) {
11975      if (!name) {
11976        return;
11977      }
11978
11979      if (Tech.techs_ && Tech.techs_[name]) {
11980        return Tech.techs_[name];
11981      }
11982
11983      name = toTitleCase$1(name);
11984
11985      if (window_1 && window_1.videojs && window_1.videojs[name]) {
11986        log$1.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
11987        return window_1.videojs[name];
11988      }
11989    };
11990
11991    return Tech;
11992  }(Component$1);
11993  /**
11994   * Get the {@link VideoTrackList}
11995   *
11996   * @returns {VideoTrackList}
11997   * @method Tech.prototype.videoTracks
11998   */
11999
12000  /**
12001   * Get the {@link AudioTrackList}
12002   *
12003   * @returns {AudioTrackList}
12004   * @method Tech.prototype.audioTracks
12005   */
12006
12007  /**
12008   * Get the {@link TextTrackList}
12009   *
12010   * @returns {TextTrackList}
12011   * @method Tech.prototype.textTracks
12012   */
12013
12014  /**
12015   * Get the remote element {@link TextTrackList}
12016   *
12017   * @returns {TextTrackList}
12018   * @method Tech.prototype.remoteTextTracks
12019   */
12020
12021  /**
12022   * Get the remote element {@link HtmlTrackElementList}
12023   *
12024   * @returns {HtmlTrackElementList}
12025   * @method Tech.prototype.remoteTextTrackEls
12026   */
12027
12028
12029  ALL.names.forEach(function (name) {
12030    var props = ALL[name];
12031
12032    Tech.prototype[props.getterName] = function () {
12033      this[props.privateName] = this[props.privateName] || new props.ListClass();
12034      return this[props.privateName];
12035    };
12036  });
12037  /**
12038   * List of associated text tracks
12039   *
12040   * @type {TextTrackList}
12041   * @private
12042   * @property Tech#textTracks_
12043   */
12044
12045  /**
12046   * List of associated audio tracks.
12047   *
12048   * @type {AudioTrackList}
12049   * @private
12050   * @property Tech#audioTracks_
12051   */
12052
12053  /**
12054   * List of associated video tracks.
12055   *
12056   * @type {VideoTrackList}
12057   * @private
12058   * @property Tech#videoTracks_
12059   */
12060
12061  /**
12062   * Boolean indicating whether the `Tech` supports volume control.
12063   *
12064   * @type {boolean}
12065   * @default
12066   */
12067
12068  Tech.prototype.featuresVolumeControl = true;
12069  /**
12070   * Boolean indicating whether the `Tech` supports muting volume.
12071   *
12072   * @type {bolean}
12073   * @default
12074   */
12075
12076  Tech.prototype.featuresMuteControl = true;
12077  /**
12078   * Boolean indicating whether the `Tech` supports fullscreen resize control.
12079   * Resizing plugins using request fullscreen reloads the plugin
12080   *
12081   * @type {boolean}
12082   * @default
12083   */
12084
12085  Tech.prototype.featuresFullscreenResize = false;
12086  /**
12087   * Boolean indicating whether the `Tech` supports changing the speed at which the video
12088   * plays. Examples:
12089   *   - Set player to play 2x (twice) as fast
12090   *   - Set player to play 0.5x (half) as fast
12091   *
12092   * @type {boolean}
12093   * @default
12094   */
12095
12096  Tech.prototype.featuresPlaybackRate = false;
12097  /**
12098   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
12099   * not triggered by video-js-swf. This will be used to determine if
12100   * {@link Tech#manualProgressOn} should be called.
12101   *
12102   * @type {boolean}
12103   * @default
12104   */
12105
12106  Tech.prototype.featuresProgressEvents = false;
12107  /**
12108   * Boolean indicating whether the `Tech` supports the `sourceset` event.
12109   *
12110   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
12111   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
12112   * a new source.
12113   *
12114   * @type {boolean}
12115   * @default
12116   */
12117
12118  Tech.prototype.featuresSourceset = false;
12119  /**
12120   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
12121   * not triggered by video-js-swf. This will be used to determine if
12122   * {@link Tech#manualTimeUpdates} should be called.
12123   *
12124   * @type {boolean}
12125   * @default
12126   */
12127
12128  Tech.prototype.featuresTimeupdateEvents = false;
12129  /**
12130   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
12131   * This will help us integrate with native `TextTrack`s if the browser supports them.
12132   *
12133   * @type {boolean}
12134   * @default
12135   */
12136
12137  Tech.prototype.featuresNativeTextTracks = false;
12138  /**
12139   * A functional mixin for techs that want to use the Source Handler pattern.
12140   * Source handlers are scripts for handling specific formats.
12141   * The source handler pattern is used for adaptive formats (HLS, DASH) that
12142   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
12143   * Example: `Tech.withSourceHandlers.call(MyTech);`
12144   *
12145   * @param {Tech} _Tech
12146   *        The tech to add source handler functions to.
12147   *
12148   * @mixes Tech~SourceHandlerAdditions
12149   */
12150
12151  Tech.withSourceHandlers = function (_Tech) {
12152    /**
12153     * Register a source handler
12154     *
12155     * @param {Function} handler
12156     *        The source handler class
12157     *
12158     * @param {number} [index]
12159     *        Register it at the following index
12160     */
12161    _Tech.registerSourceHandler = function (handler, index) {
12162      var handlers = _Tech.sourceHandlers;
12163
12164      if (!handlers) {
12165        handlers = _Tech.sourceHandlers = [];
12166      }
12167
12168      if (index === undefined) {
12169        // add to the end of the list
12170        index = handlers.length;
12171      }
12172
12173      handlers.splice(index, 0, handler);
12174    };
12175    /**
12176     * Check if the tech can support the given type. Also checks the
12177     * Techs sourceHandlers.
12178     *
12179     * @param {string} type
12180     *         The mimetype to check.
12181     *
12182     * @return {string}
12183     *         'probably', 'maybe', or '' (empty string)
12184     */
12185
12186
12187    _Tech.canPlayType = function (type) {
12188      var handlers = _Tech.sourceHandlers || [];
12189      var can;
12190
12191      for (var i = 0; i < handlers.length; i++) {
12192        can = handlers[i].canPlayType(type);
12193
12194        if (can) {
12195          return can;
12196        }
12197      }
12198
12199      return '';
12200    };
12201    /**
12202     * Returns the first source handler that supports the source.
12203     *
12204     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
12205     *
12206     * @param {Tech~SourceObject} source
12207     *        The source object
12208     *
12209     * @param {Object} options
12210     *        The options passed to the tech
12211     *
12212     * @return {SourceHandler|null}
12213     *          The first source handler that supports the source or null if
12214     *          no SourceHandler supports the source
12215     */
12216
12217
12218    _Tech.selectSourceHandler = function (source, options) {
12219      var handlers = _Tech.sourceHandlers || [];
12220      var can;
12221
12222      for (var i = 0; i < handlers.length; i++) {
12223        can = handlers[i].canHandleSource(source, options);
12224
12225        if (can) {
12226          return handlers[i];
12227        }
12228      }
12229
12230      return null;
12231    };
12232    /**
12233     * Check if the tech can support the given source.
12234     *
12235     * @param {Tech~SourceObject} srcObj
12236     *        The source object
12237     *
12238     * @param {Object} options
12239     *        The options passed to the tech
12240     *
12241     * @return {string}
12242     *         'probably', 'maybe', or '' (empty string)
12243     */
12244
12245
12246    _Tech.canPlaySource = function (srcObj, options) {
12247      var sh = _Tech.selectSourceHandler(srcObj, options);
12248
12249      if (sh) {
12250        return sh.canHandleSource(srcObj, options);
12251      }
12252
12253      return '';
12254    };
12255    /**
12256     * When using a source handler, prefer its implementation of
12257     * any function normally provided by the tech.
12258     */
12259
12260
12261    var deferrable = ['seekable', 'seeking', 'duration'];
12262    /**
12263     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
12264     * function if it exists, with a fallback to the Techs seekable function.
12265     *
12266     * @method _Tech.seekable
12267     */
12268
12269    /**
12270     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
12271     * function if it exists, otherwise it will fallback to the techs duration function.
12272     *
12273     * @method _Tech.duration
12274     */
12275
12276    deferrable.forEach(function (fnName) {
12277      var originalFn = this[fnName];
12278
12279      if (typeof originalFn !== 'function') {
12280        return;
12281      }
12282
12283      this[fnName] = function () {
12284        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
12285          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
12286        }
12287
12288        return originalFn.apply(this, arguments);
12289      };
12290    }, _Tech.prototype);
12291    /**
12292     * Create a function for setting the source using a source object
12293     * and source handlers.
12294     * Should never be called unless a source handler was found.
12295     *
12296     * @param {Tech~SourceObject} source
12297     *        A source object with src and type keys
12298     */
12299
12300    _Tech.prototype.setSource = function (source) {
12301      var sh = _Tech.selectSourceHandler(source, this.options_);
12302
12303      if (!sh) {
12304        // Fall back to a native source hander when unsupported sources are
12305        // deliberately set
12306        if (_Tech.nativeSourceHandler) {
12307          sh = _Tech.nativeSourceHandler;
12308        } else {
12309          log$1.error('No source handler found for the current source.');
12310        }
12311      } // Dispose any existing source handler
12312
12313
12314      this.disposeSourceHandler();
12315      this.off('dispose', this.disposeSourceHandler_);
12316
12317      if (sh !== _Tech.nativeSourceHandler) {
12318        this.currentSource_ = source;
12319      }
12320
12321      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
12322      this.one('dispose', this.disposeSourceHandler_);
12323    };
12324    /**
12325     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
12326     *
12327     * @listens Tech#dispose
12328     */
12329
12330
12331    _Tech.prototype.disposeSourceHandler = function () {
12332      // if we have a source and get another one
12333      // then we are loading something new
12334      // than clear all of our current tracks
12335      if (this.currentSource_) {
12336        this.clearTracks(['audio', 'video']);
12337        this.currentSource_ = null;
12338      } // always clean up auto-text tracks
12339
12340
12341      this.cleanupAutoTextTracks();
12342
12343      if (this.sourceHandler_) {
12344        if (this.sourceHandler_.dispose) {
12345          this.sourceHandler_.dispose();
12346        }
12347
12348        this.sourceHandler_ = null;
12349      }
12350    };
12351  }; // The base Tech class needs to be registered as a Component. It is the only
12352  // Tech that can be registered as a Component.
12353
12354
12355  Component$1.registerComponent('Tech', Tech);
12356  Tech.registerTech('Tech', Tech);
12357  /**
12358   * A list of techs that should be added to techOrder on Players
12359   *
12360   * @private
12361   */
12362
12363  Tech.defaultTechOrder_ = [];
12364
12365  /**
12366   * @file middleware.js
12367   * @module middleware
12368   */
12369  var middlewares = {};
12370  var middlewareInstances = {};
12371  var TERMINATOR = {};
12372  /**
12373   * A middleware object is a plain JavaScript object that has methods that
12374   * match the {@link Tech} methods found in the lists of allowed
12375   * {@link module:middleware.allowedGetters|getters},
12376   * {@link module:middleware.allowedSetters|setters}, and
12377   * {@link module:middleware.allowedMediators|mediators}.
12378   *
12379   * @typedef {Object} MiddlewareObject
12380   */
12381
12382  /**
12383   * A middleware factory function that should return a
12384   * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
12385   *
12386   * This factory will be called for each player when needed, with the player
12387   * passed in as an argument.
12388   *
12389   * @callback MiddlewareFactory
12390   * @param {Player} player
12391   *        A Video.js player.
12392   */
12393
12394  /**
12395   * Define a middleware that the player should use by way of a factory function
12396   * that returns a middleware object.
12397   *
12398   * @param  {string} type
12399   *         The MIME type to match or `"*"` for all MIME types.
12400   *
12401   * @param  {MiddlewareFactory} middleware
12402   *         A middleware factory function that will be executed for
12403   *         matching types.
12404   */
12405
12406  function use(type, middleware) {
12407    middlewares[type] = middlewares[type] || [];
12408    middlewares[type].push(middleware);
12409  }
12410  /**
12411   * Asynchronously sets a source using middleware by recursing through any
12412   * matching middlewares and calling `setSource` on each, passing along the
12413   * previous returned value each time.
12414   *
12415   * @param  {Player} player
12416   *         A {@link Player} instance.
12417   *
12418   * @param  {Tech~SourceObject} src
12419   *         A source object.
12420   *
12421   * @param  {Function}
12422   *         The next middleware to run.
12423   */
12424
12425  function setSource(player, src, next) {
12426    player.setTimeout(function () {
12427      return setSourceHelper(src, middlewares[src.type], next, player);
12428    }, 1);
12429  }
12430  /**
12431   * When the tech is set, passes the tech to each middleware's `setTech` method.
12432   *
12433   * @param {Object[]} middleware
12434   *        An array of middleware instances.
12435   *
12436   * @param {Tech} tech
12437   *        A Video.js tech.
12438   */
12439
12440  function setTech(middleware, tech) {
12441    middleware.forEach(function (mw) {
12442      return mw.setTech && mw.setTech(tech);
12443    });
12444  }
12445  /**
12446   * Calls a getter on the tech first, through each middleware
12447   * from right to left to the player.
12448   *
12449   * @param  {Object[]} middleware
12450   *         An array of middleware instances.
12451   *
12452   * @param  {Tech} tech
12453   *         The current tech.
12454   *
12455   * @param  {string} method
12456   *         A method name.
12457   *
12458   * @return {Mixed}
12459   *         The final value from the tech after middleware has intercepted it.
12460   */
12461
12462  function get(middleware, tech, method) {
12463    return middleware.reduceRight(middlewareIterator(method), tech[method]());
12464  }
12465  /**
12466   * Takes the argument given to the player and calls the setter method on each
12467   * middleware from left to right to the tech.
12468   *
12469   * @param  {Object[]} middleware
12470   *         An array of middleware instances.
12471   *
12472   * @param  {Tech} tech
12473   *         The current tech.
12474   *
12475   * @param  {string} method
12476   *         A method name.
12477   *
12478   * @param  {Mixed} arg
12479   *         The value to set on the tech.
12480   *
12481   * @return {Mixed}
12482   *         The return value of the `method` of the `tech`.
12483   */
12484
12485  function set(middleware, tech, method, arg) {
12486    return tech[method](middleware.reduce(middlewareIterator(method), arg));
12487  }
12488  /**
12489   * Takes the argument given to the player and calls the `call` version of the
12490   * method on each middleware from left to right.
12491   *
12492   * Then, call the passed in method on the tech and return the result unchanged
12493   * back to the player, through middleware, this time from right to left.
12494   *
12495   * @param  {Object[]} middleware
12496   *         An array of middleware instances.
12497   *
12498   * @param  {Tech} tech
12499   *         The current tech.
12500   *
12501   * @param  {string} method
12502   *         A method name.
12503   *
12504   * @param  {Mixed} arg
12505   *         The value to set on the tech.
12506   *
12507   * @return {Mixed}
12508   *         The return value of the `method` of the `tech`, regardless of the
12509   *         return values of middlewares.
12510   */
12511
12512  function mediate(middleware, tech, method, arg) {
12513    if (arg === void 0) {
12514      arg = null;
12515    }
12516
12517    var callMethod = 'call' + toTitleCase$1(method);
12518    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
12519    var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
12520    // prevent confusion if a techs method actually returns null.
12521
12522    var returnValue = terminated ? null : tech[method](middlewareValue);
12523    executeRight(middleware, method, returnValue, terminated);
12524    return returnValue;
12525  }
12526  /**
12527   * Enumeration of allowed getters where the keys are method names.
12528   *
12529   * @type {Object}
12530   */
12531
12532  var allowedGetters = {
12533    buffered: 1,
12534    currentTime: 1,
12535    duration: 1,
12536    muted: 1,
12537    played: 1,
12538    paused: 1,
12539    seekable: 1,
12540    volume: 1,
12541    ended: 1
12542  };
12543  /**
12544   * Enumeration of allowed setters where the keys are method names.
12545   *
12546   * @type {Object}
12547   */
12548
12549  var allowedSetters = {
12550    setCurrentTime: 1,
12551    setMuted: 1,
12552    setVolume: 1
12553  };
12554  /**
12555   * Enumeration of allowed mediators where the keys are method names.
12556   *
12557   * @type {Object}
12558   */
12559
12560  var allowedMediators = {
12561    play: 1,
12562    pause: 1
12563  };
12564
12565  function middlewareIterator(method) {
12566    return function (value, mw) {
12567      // if the previous middleware terminated, pass along the termination
12568      if (value === TERMINATOR) {
12569        return TERMINATOR;
12570      }
12571
12572      if (mw[method]) {
12573        return mw[method](value);
12574      }
12575
12576      return value;
12577    };
12578  }
12579
12580  function executeRight(mws, method, value, terminated) {
12581    for (var i = mws.length - 1; i >= 0; i--) {
12582      var mw = mws[i];
12583
12584      if (mw[method]) {
12585        mw[method](terminated, value);
12586      }
12587    }
12588  }
12589  /**
12590   * Clear the middleware cache for a player.
12591   *
12592   * @param  {Player} player
12593   *         A {@link Player} instance.
12594   */
12595
12596
12597  function clearCacheForPlayer(player) {
12598    middlewareInstances[player.id()] = null;
12599  }
12600  /**
12601   * {
12602   *  [playerId]: [[mwFactory, mwInstance], ...]
12603   * }
12604   *
12605   * @private
12606   */
12607
12608  function getOrCreateFactory(player, mwFactory) {
12609    var mws = middlewareInstances[player.id()];
12610    var mw = null;
12611
12612    if (mws === undefined || mws === null) {
12613      mw = mwFactory(player);
12614      middlewareInstances[player.id()] = [[mwFactory, mw]];
12615      return mw;
12616    }
12617
12618    for (var i = 0; i < mws.length; i++) {
12619      var _mws$i = mws[i],
12620          mwf = _mws$i[0],
12621          mwi = _mws$i[1];
12622
12623      if (mwf !== mwFactory) {
12624        continue;
12625      }
12626
12627      mw = mwi;
12628    }
12629
12630    if (mw === null) {
12631      mw = mwFactory(player);
12632      mws.push([mwFactory, mw]);
12633    }
12634
12635    return mw;
12636  }
12637
12638  function setSourceHelper(src, middleware, next, player, acc, lastRun) {
12639    if (src === void 0) {
12640      src = {};
12641    }
12642
12643    if (middleware === void 0) {
12644      middleware = [];
12645    }
12646
12647    if (acc === void 0) {
12648      acc = [];
12649    }
12650
12651    if (lastRun === void 0) {
12652      lastRun = false;
12653    }
12654
12655    var _middleware = middleware,
12656        mwFactory = _middleware[0],
12657        mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
12658
12659
12660    if (typeof mwFactory === 'string') {
12661      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
12662      // then call the mw's setSource method
12663    } else if (mwFactory) {
12664      var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
12665
12666      if (!mw.setSource) {
12667        acc.push(mw);
12668        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12669      }
12670
12671      mw.setSource(assign({}, src), function (err, _src) {
12672        // something happened, try the next middleware on the current level
12673        // make sure to use the old src
12674        if (err) {
12675          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12676        } // we've succeeded, now we need to go deeper
12677
12678
12679        acc.push(mw); // if it's the same type, continue down the current chain
12680        // otherwise, we want to go down the new chain
12681
12682        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
12683      });
12684    } else if (mwrest.length) {
12685      setSourceHelper(src, mwrest, next, player, acc, lastRun);
12686    } else if (lastRun) {
12687      next(src, acc);
12688    } else {
12689      setSourceHelper(src, middlewares['*'], next, player, acc, true);
12690    }
12691  }
12692
12693  /**
12694   * Mimetypes
12695   *
12696   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
12697   * @typedef Mimetypes~Kind
12698   * @enum
12699   */
12700
12701  var MimetypesKind = {
12702    opus: 'video/ogg',
12703    ogv: 'video/ogg',
12704    mp4: 'video/mp4',
12705    mov: 'video/mp4',
12706    m4v: 'video/mp4',
12707    mkv: 'video/x-matroska',
12708    m4a: 'audio/mp4',
12709    mp3: 'audio/mpeg',
12710    aac: 'audio/aac',
12711    caf: 'audio/x-caf',
12712    flac: 'audio/flac',
12713    oga: 'audio/ogg',
12714    wav: 'audio/wav',
12715    m3u8: 'application/x-mpegURL',
12716    jpg: 'image/jpeg',
12717    jpeg: 'image/jpeg',
12718    gif: 'image/gif',
12719    png: 'image/png',
12720    svg: 'image/svg+xml',
12721    webp: 'image/webp'
12722  };
12723  /**
12724   * Get the mimetype of a given src url if possible
12725   *
12726   * @param {string} src
12727   *        The url to the src
12728   *
12729   * @return {string}
12730   *         return the mimetype if it was known or empty string otherwise
12731   */
12732
12733  var getMimetype = function getMimetype(src) {
12734    if (src === void 0) {
12735      src = '';
12736    }
12737
12738    var ext = getFileExtension(src);
12739    var mimetype = MimetypesKind[ext.toLowerCase()];
12740    return mimetype || '';
12741  };
12742  /**
12743   * Find the mime type of a given source string if possible. Uses the player
12744   * source cache.
12745   *
12746   * @param {Player} player
12747   *        The player object
12748   *
12749   * @param {string} src
12750   *        The source string
12751   *
12752   * @return {string}
12753   *         The type that was found
12754   */
12755
12756  var findMimetype = function findMimetype(player, src) {
12757    if (!src) {
12758      return '';
12759    } // 1. check for the type in the `source` cache
12760
12761
12762    if (player.cache_.source.src === src && player.cache_.source.type) {
12763      return player.cache_.source.type;
12764    } // 2. see if we have this source in our `currentSources` cache
12765
12766
12767    var matchingSources = player.cache_.sources.filter(function (s) {
12768      return s.src === src;
12769    });
12770
12771    if (matchingSources.length) {
12772      return matchingSources[0].type;
12773    } // 3. look for the src url in source elements and use the type there
12774
12775
12776    var sources = player.$$('source');
12777
12778    for (var i = 0; i < sources.length; i++) {
12779      var s = sources[i];
12780
12781      if (s.type && s.src && s.src === src) {
12782        return s.type;
12783      }
12784    } // 4. finally fallback to our list of mime types based on src url extension
12785
12786
12787    return getMimetype(src);
12788  };
12789
12790  /**
12791   * @module filter-source
12792   */
12793  /**
12794   * Filter out single bad source objects or multiple source objects in an
12795   * array. Also flattens nested source object arrays into a 1 dimensional
12796   * array of source objects.
12797   *
12798   * @param {Tech~SourceObject|Tech~SourceObject[]} src
12799   *        The src object to filter
12800   *
12801   * @return {Tech~SourceObject[]}
12802   *         An array of sourceobjects containing only valid sources
12803   *
12804   * @private
12805   */
12806
12807  var filterSource = function filterSource(src) {
12808    // traverse array
12809    if (Array.isArray(src)) {
12810      var newsrc = [];
12811      src.forEach(function (srcobj) {
12812        srcobj = filterSource(srcobj);
12813
12814        if (Array.isArray(srcobj)) {
12815          newsrc = newsrc.concat(srcobj);
12816        } else if (isObject$1(srcobj)) {
12817          newsrc.push(srcobj);
12818        }
12819      });
12820      src = newsrc;
12821    } else if (typeof src === 'string' && src.trim()) {
12822      // convert string into object
12823      src = [fixSource({
12824        src: src
12825      })];
12826    } else if (isObject$1(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
12827      // src is already valid
12828      src = [fixSource(src)];
12829    } else {
12830      // invalid source, turn it into an empty array
12831      src = [];
12832    }
12833
12834    return src;
12835  };
12836  /**
12837   * Checks src mimetype, adding it when possible
12838   *
12839   * @param {Tech~SourceObject} src
12840   *        The src object to check
12841   * @return {Tech~SourceObject}
12842   *        src Object with known type
12843   */
12844
12845
12846  function fixSource(src) {
12847    if (!src.type) {
12848      var mimetype = getMimetype(src.src);
12849
12850      if (mimetype) {
12851        src.type = mimetype;
12852      }
12853    }
12854
12855    return src;
12856  }
12857
12858  /**
12859   * The `MediaLoader` is the `Component` that decides which playback technology to load
12860   * when a player is initialized.
12861   *
12862   * @extends Component
12863   */
12864
12865  var MediaLoader = /*#__PURE__*/function (_Component) {
12866    inheritsLoose(MediaLoader, _Component);
12867
12868    /**
12869     * Create an instance of this class.
12870     *
12871     * @param {Player} player
12872     *        The `Player` that this class should attach to.
12873     *
12874     * @param {Object} [options]
12875     *        The key/value store of player options.
12876     *
12877     * @param {Component~ReadyCallback} [ready]
12878     *        The function that is run when this component is ready.
12879     */
12880    function MediaLoader(player, options, ready) {
12881      var _this;
12882
12883      // MediaLoader has no element
12884      var options_ = mergeOptions$3({
12885        createEl: false
12886      }, options);
12887      _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
12888      // load the first supported playback technology.
12889
12890      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
12891        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
12892          var techName = toTitleCase$1(j[i]);
12893          var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
12894          // Remove once that deprecated behavior is removed.
12895
12896          if (!techName) {
12897            tech = Component$1.getComponent(techName);
12898          } // Check if the browser supports this technology
12899
12900
12901          if (tech && tech.isSupported()) {
12902            player.loadTech_(techName);
12903            break;
12904          }
12905        }
12906      } else {
12907        // Loop through playback technologies (e.g. HTML5) and check for support.
12908        // Then load the best source.
12909        // A few assumptions here:
12910        //   All playback technologies respect preload false.
12911        player.src(options.playerOptions.sources);
12912      }
12913
12914      return _this;
12915    }
12916
12917    return MediaLoader;
12918  }(Component$1);
12919
12920  Component$1.registerComponent('MediaLoader', MediaLoader);
12921
12922  /**
12923   * Component which is clickable or keyboard actionable, but is not a
12924   * native HTML button.
12925   *
12926   * @extends Component
12927   */
12928
12929  var ClickableComponent = /*#__PURE__*/function (_Component) {
12930    inheritsLoose(ClickableComponent, _Component);
12931
12932    /**
12933     * Creates an instance of this class.
12934     *
12935     * @param  {Player} player
12936     *         The `Player` that this class should be attached to.
12937     *
12938     * @param  {Object} [options]
12939     *         The key/value store of player options.
12940     *
12941     * @param  {function} [options.clickHandler]
12942     *         The function to call when the button is clicked / activated
12943     */
12944    function ClickableComponent(player, options) {
12945      var _this;
12946
12947      _this = _Component.call(this, player, options) || this;
12948
12949      _this.handleMouseOver_ = function (e) {
12950        return _this.handleMouseOver(e);
12951      };
12952
12953      _this.handleMouseOut_ = function (e) {
12954        return _this.handleMouseOut(e);
12955      };
12956
12957      _this.handleClick_ = function (e) {
12958        return _this.handleClick(e);
12959      };
12960
12961      _this.handleKeyDown_ = function (e) {
12962        return _this.handleKeyDown(e);
12963      };
12964
12965      _this.emitTapEvents();
12966
12967      _this.enable();
12968
12969      return _this;
12970    }
12971    /**
12972     * Create the `ClickableComponent`s DOM element.
12973     *
12974     * @param {string} [tag=div]
12975     *        The element's node type.
12976     *
12977     * @param {Object} [props={}]
12978     *        An object of properties that should be set on the element.
12979     *
12980     * @param {Object} [attributes={}]
12981     *        An object of attributes that should be set on the element.
12982     *
12983     * @return {Element}
12984     *         The element that gets created.
12985     */
12986
12987
12988    var _proto = ClickableComponent.prototype;
12989
12990    _proto.createEl = function createEl$1(tag, props, attributes) {
12991      if (tag === void 0) {
12992        tag = 'div';
12993      }
12994
12995      if (props === void 0) {
12996        props = {};
12997      }
12998
12999      if (attributes === void 0) {
13000        attributes = {};
13001      }
13002
13003      props = assign({
13004        className: this.buildCSSClass(),
13005        tabIndex: 0
13006      }, props);
13007
13008      if (tag === 'button') {
13009        log$1.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
13010      } // Add ARIA attributes for clickable element which is not a native HTML button
13011
13012
13013      attributes = assign({
13014        role: 'button'
13015      }, attributes);
13016      this.tabIndex_ = props.tabIndex;
13017      var el = createEl(tag, props, attributes);
13018      el.appendChild(createEl('span', {
13019        className: 'vjs-icon-placeholder'
13020      }, {
13021        'aria-hidden': true
13022      }));
13023      this.createControlTextEl(el);
13024      return el;
13025    };
13026
13027    _proto.dispose = function dispose() {
13028      // remove controlTextEl_ on dispose
13029      this.controlTextEl_ = null;
13030
13031      _Component.prototype.dispose.call(this);
13032    }
13033    /**
13034     * Create a control text element on this `ClickableComponent`
13035     *
13036     * @param {Element} [el]
13037     *        Parent element for the control text.
13038     *
13039     * @return {Element}
13040     *         The control text element that gets created.
13041     */
13042    ;
13043
13044    _proto.createControlTextEl = function createControlTextEl(el) {
13045      this.controlTextEl_ = createEl('span', {
13046        className: 'vjs-control-text'
13047      }, {
13048        // let the screen reader user know that the text of the element may change
13049        'aria-live': 'polite'
13050      });
13051
13052      if (el) {
13053        el.appendChild(this.controlTextEl_);
13054      }
13055
13056      this.controlText(this.controlText_, el);
13057      return this.controlTextEl_;
13058    }
13059    /**
13060     * Get or set the localize text to use for the controls on the `ClickableComponent`.
13061     *
13062     * @param {string} [text]
13063     *        Control text for element.
13064     *
13065     * @param {Element} [el=this.el()]
13066     *        Element to set the title on.
13067     *
13068     * @return {string}
13069     *         - The control text when getting
13070     */
13071    ;
13072
13073    _proto.controlText = function controlText(text, el) {
13074      if (el === void 0) {
13075        el = this.el();
13076      }
13077
13078      if (text === undefined) {
13079        return this.controlText_ || 'Need Text';
13080      }
13081
13082      var localizedText = this.localize(text);
13083      this.controlText_ = text;
13084      textContent(this.controlTextEl_, localizedText);
13085
13086      if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
13087        // Set title attribute if only an icon is shown
13088        el.setAttribute('title', localizedText);
13089      }
13090    }
13091    /**
13092     * Builds the default DOM `className`.
13093     *
13094     * @return {string}
13095     *         The DOM `className` for this object.
13096     */
13097    ;
13098
13099    _proto.buildCSSClass = function buildCSSClass() {
13100      return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
13101    }
13102    /**
13103     * Enable this `ClickableComponent`
13104     */
13105    ;
13106
13107    _proto.enable = function enable() {
13108      if (!this.enabled_) {
13109        this.enabled_ = true;
13110        this.removeClass('vjs-disabled');
13111        this.el_.setAttribute('aria-disabled', 'false');
13112
13113        if (typeof this.tabIndex_ !== 'undefined') {
13114          this.el_.setAttribute('tabIndex', this.tabIndex_);
13115        }
13116
13117        this.on(['tap', 'click'], this.handleClick_);
13118        this.on('keydown', this.handleKeyDown_);
13119      }
13120    }
13121    /**
13122     * Disable this `ClickableComponent`
13123     */
13124    ;
13125
13126    _proto.disable = function disable() {
13127      this.enabled_ = false;
13128      this.addClass('vjs-disabled');
13129      this.el_.setAttribute('aria-disabled', 'true');
13130
13131      if (typeof this.tabIndex_ !== 'undefined') {
13132        this.el_.removeAttribute('tabIndex');
13133      }
13134
13135      this.off('mouseover', this.handleMouseOver_);
13136      this.off('mouseout', this.handleMouseOut_);
13137      this.off(['tap', 'click'], this.handleClick_);
13138      this.off('keydown', this.handleKeyDown_);
13139    }
13140    /**
13141     * Handles language change in ClickableComponent for the player in components
13142     *
13143     *
13144     */
13145    ;
13146
13147    _proto.handleLanguagechange = function handleLanguagechange() {
13148      this.controlText(this.controlText_);
13149    }
13150    /**
13151     * Event handler that is called when a `ClickableComponent` receives a
13152     * `click` or `tap` event.
13153     *
13154     * @param {EventTarget~Event} event
13155     *        The `tap` or `click` event that caused this function to be called.
13156     *
13157     * @listens tap
13158     * @listens click
13159     * @abstract
13160     */
13161    ;
13162
13163    _proto.handleClick = function handleClick(event) {
13164      if (this.options_.clickHandler) {
13165        this.options_.clickHandler.call(this, arguments);
13166      }
13167    }
13168    /**
13169     * Event handler that is called when a `ClickableComponent` receives a
13170     * `keydown` event.
13171     *
13172     * By default, if the key is Space or Enter, it will trigger a `click` event.
13173     *
13174     * @param {EventTarget~Event} event
13175     *        The `keydown` event that caused this function to be called.
13176     *
13177     * @listens keydown
13178     */
13179    ;
13180
13181    _proto.handleKeyDown = function handleKeyDown(event) {
13182      // Support Space or Enter key operation to fire a click event. Also,
13183      // prevent the event from propagating through the DOM and triggering
13184      // Player hotkeys.
13185      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
13186        event.preventDefault();
13187        event.stopPropagation();
13188        this.trigger('click');
13189      } else {
13190        // Pass keypress handling up for unsupported keys
13191        _Component.prototype.handleKeyDown.call(this, event);
13192      }
13193    };
13194
13195    return ClickableComponent;
13196  }(Component$1);
13197
13198  Component$1.registerComponent('ClickableComponent', ClickableComponent);
13199
13200  /**
13201   * A `ClickableComponent` that handles showing the poster image for the player.
13202   *
13203   * @extends ClickableComponent
13204   */
13205
13206  var PosterImage = /*#__PURE__*/function (_ClickableComponent) {
13207    inheritsLoose(PosterImage, _ClickableComponent);
13208
13209    /**
13210     * Create an instance of this class.
13211     *
13212     * @param {Player} player
13213     *        The `Player` that this class should attach to.
13214     *
13215     * @param {Object} [options]
13216     *        The key/value store of player options.
13217     */
13218    function PosterImage(player, options) {
13219      var _this;
13220
13221      _this = _ClickableComponent.call(this, player, options) || this;
13222
13223      _this.update();
13224
13225      _this.update_ = function (e) {
13226        return _this.update(e);
13227      };
13228
13229      player.on('posterchange', _this.update_);
13230      return _this;
13231    }
13232    /**
13233     * Clean up and dispose of the `PosterImage`.
13234     */
13235
13236
13237    var _proto = PosterImage.prototype;
13238
13239    _proto.dispose = function dispose() {
13240      this.player().off('posterchange', this.update_);
13241
13242      _ClickableComponent.prototype.dispose.call(this);
13243    }
13244    /**
13245     * Create the `PosterImage`s DOM element.
13246     *
13247     * @return {Element}
13248     *         The element that gets created.
13249     */
13250    ;
13251
13252    _proto.createEl = function createEl$1() {
13253      var el = createEl('div', {
13254        className: 'vjs-poster',
13255        // Don't want poster to be tabbable.
13256        tabIndex: -1
13257      });
13258      return el;
13259    }
13260    /**
13261     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
13262     *
13263     * @listens Player#posterchange
13264     *
13265     * @param {EventTarget~Event} [event]
13266     *        The `Player#posterchange` event that triggered this function.
13267     */
13268    ;
13269
13270    _proto.update = function update(event) {
13271      var url = this.player().poster();
13272      this.setSrc(url); // If there's no poster source we should display:none on this component
13273      // so it's not still clickable or right-clickable
13274
13275      if (url) {
13276        this.show();
13277      } else {
13278        this.hide();
13279      }
13280    }
13281    /**
13282     * Set the source of the `PosterImage` depending on the display method.
13283     *
13284     * @param {string} url
13285     *        The URL to the source for the `PosterImage`.
13286     */
13287    ;
13288
13289    _proto.setSrc = function setSrc(url) {
13290      var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
13291      // this will throw an extra error
13292
13293      if (url) {
13294        backgroundImage = "url(\"" + url + "\")";
13295      }
13296
13297      this.el_.style.backgroundImage = backgroundImage;
13298    }
13299    /**
13300     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
13301     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
13302     *
13303     * @listens tap
13304     * @listens click
13305     * @listens keydown
13306     *
13307     * @param {EventTarget~Event} event
13308     +        The `click`, `tap` or `keydown` event that caused this function to be called.
13309     */
13310    ;
13311
13312    _proto.handleClick = function handleClick(event) {
13313      // We don't want a click to trigger playback when controls are disabled
13314      if (!this.player_.controls()) {
13315        return;
13316      }
13317
13318      var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
13319
13320      if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
13321      // calling .focus() on the video element causes the video to go black,
13322      // so we avoid it in that specific case
13323      !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
13324        this.player_.tech(true).focus();
13325      }
13326
13327      if (this.player_.paused()) {
13328        silencePromise(this.player_.play());
13329      } else {
13330        this.player_.pause();
13331      }
13332    };
13333
13334    return PosterImage;
13335  }(ClickableComponent);
13336
13337  Component$1.registerComponent('PosterImage', PosterImage);
13338
13339  var darkGray = '#222';
13340  var lightGray = '#ccc';
13341  var fontMap = {
13342    monospace: 'monospace',
13343    sansSerif: 'sans-serif',
13344    serif: 'serif',
13345    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
13346    monospaceSerif: '"Courier New", monospace',
13347    proportionalSansSerif: 'sans-serif',
13348    proportionalSerif: 'serif',
13349    casual: '"Comic Sans MS", Impact, fantasy',
13350    script: '"Monotype Corsiva", cursive',
13351    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
13352  };
13353  /**
13354   * Construct an rgba color from a given hex color code.
13355   *
13356   * @param {number} color
13357   *        Hex number for color, like #f0e or #f604e2.
13358   *
13359   * @param {number} opacity
13360   *        Value for opacity, 0.0 - 1.0.
13361   *
13362   * @return {string}
13363   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
13364   */
13365
13366  function constructColor(color, opacity) {
13367    var hex;
13368
13369    if (color.length === 4) {
13370      // color looks like "#f0e"
13371      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
13372    } else if (color.length === 7) {
13373      // color looks like "#f604e2"
13374      hex = color.slice(1);
13375    } else {
13376      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
13377    }
13378
13379    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
13380  }
13381  /**
13382   * Try to update the style of a DOM element. Some style changes will throw an error,
13383   * particularly in IE8. Those should be noops.
13384   *
13385   * @param {Element} el
13386   *        The DOM element to be styled.
13387   *
13388   * @param {string} style
13389   *        The CSS property on the element that should be styled.
13390   *
13391   * @param {string} rule
13392   *        The style rule that should be applied to the property.
13393   *
13394   * @private
13395   */
13396
13397  function tryUpdateStyle(el, style, rule) {
13398    try {
13399      el.style[style] = rule;
13400    } catch (e) {
13401      // Satisfies linter.
13402      return;
13403    }
13404  }
13405  /**
13406   * The component for displaying text track cues.
13407   *
13408   * @extends Component
13409   */
13410
13411
13412  var TextTrackDisplay = /*#__PURE__*/function (_Component) {
13413    inheritsLoose(TextTrackDisplay, _Component);
13414
13415    /**
13416     * Creates an instance of this class.
13417     *
13418     * @param {Player} player
13419     *        The `Player` that this class should be attached to.
13420     *
13421     * @param {Object} [options]
13422     *        The key/value store of player options.
13423     *
13424     * @param {Component~ReadyCallback} [ready]
13425     *        The function to call when `TextTrackDisplay` is ready.
13426     */
13427    function TextTrackDisplay(player, options, ready) {
13428      var _this;
13429
13430      _this = _Component.call(this, player, options, ready) || this;
13431
13432      var updateDisplayHandler = function updateDisplayHandler(e) {
13433        return _this.updateDisplay(e);
13434      };
13435
13436      player.on('loadstart', function (e) {
13437        return _this.toggleDisplay(e);
13438      });
13439      player.on('texttrackchange', updateDisplayHandler);
13440      player.on('loadedmetadata', function (e) {
13441        return _this.preselectTrack(e);
13442      }); // This used to be called during player init, but was causing an error
13443      // if a track should show by default and the display hadn't loaded yet.
13444      // Should probably be moved to an external track loader when we support
13445      // tracks that don't need a display.
13446
13447      player.ready(bind(assertThisInitialized(_this), function () {
13448        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
13449          this.hide();
13450          return;
13451        }
13452
13453        player.on('fullscreenchange', updateDisplayHandler);
13454        player.on('playerresize', updateDisplayHandler);
13455        window_1.addEventListener('orientationchange', updateDisplayHandler);
13456        player.on('dispose', function () {
13457          return window_1.removeEventListener('orientationchange', updateDisplayHandler);
13458        });
13459        var tracks = this.options_.playerOptions.tracks || [];
13460
13461        for (var i = 0; i < tracks.length; i++) {
13462          this.player_.addRemoteTextTrack(tracks[i], true);
13463        }
13464
13465        this.preselectTrack();
13466      }));
13467      return _this;
13468    }
13469    /**
13470    * Preselect a track following this precedence:
13471    * - matches the previously selected {@link TextTrack}'s language and kind
13472    * - matches the previously selected {@link TextTrack}'s language only
13473    * - is the first default captions track
13474    * - is the first default descriptions track
13475    *
13476    * @listens Player#loadstart
13477    */
13478
13479
13480    var _proto = TextTrackDisplay.prototype;
13481
13482    _proto.preselectTrack = function preselectTrack() {
13483      var modes = {
13484        captions: 1,
13485        subtitles: 1
13486      };
13487      var trackList = this.player_.textTracks();
13488      var userPref = this.player_.cache_.selectedLanguage;
13489      var firstDesc;
13490      var firstCaptions;
13491      var preferredTrack;
13492
13493      for (var i = 0; i < trackList.length; i++) {
13494        var track = trackList[i];
13495
13496        if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
13497          // Always choose the track that matches both language and kind
13498          if (track.kind === userPref.kind) {
13499            preferredTrack = track; // or choose the first track that matches language
13500          } else if (!preferredTrack) {
13501            preferredTrack = track;
13502          } // clear everything if offTextTrackMenuItem was clicked
13503
13504        } else if (userPref && !userPref.enabled) {
13505          preferredTrack = null;
13506          firstDesc = null;
13507          firstCaptions = null;
13508        } else if (track["default"]) {
13509          if (track.kind === 'descriptions' && !firstDesc) {
13510            firstDesc = track;
13511          } else if (track.kind in modes && !firstCaptions) {
13512            firstCaptions = track;
13513          }
13514        }
13515      } // The preferredTrack matches the user preference and takes
13516      // precedence over all the other tracks.
13517      // So, display the preferredTrack before the first default track
13518      // and the subtitles/captions track before the descriptions track
13519
13520
13521      if (preferredTrack) {
13522        preferredTrack.mode = 'showing';
13523      } else if (firstCaptions) {
13524        firstCaptions.mode = 'showing';
13525      } else if (firstDesc) {
13526        firstDesc.mode = 'showing';
13527      }
13528    }
13529    /**
13530     * Turn display of {@link TextTrack}'s from the current state into the other state.
13531     * There are only two states:
13532     * - 'shown'
13533     * - 'hidden'
13534     *
13535     * @listens Player#loadstart
13536     */
13537    ;
13538
13539    _proto.toggleDisplay = function toggleDisplay() {
13540      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
13541        this.hide();
13542      } else {
13543        this.show();
13544      }
13545    }
13546    /**
13547     * Create the {@link Component}'s DOM element.
13548     *
13549     * @return {Element}
13550     *         The element that was created.
13551     */
13552    ;
13553
13554    _proto.createEl = function createEl() {
13555      return _Component.prototype.createEl.call(this, 'div', {
13556        className: 'vjs-text-track-display'
13557      }, {
13558        'aria-live': 'off',
13559        'aria-atomic': 'true'
13560      });
13561    }
13562    /**
13563     * Clear all displayed {@link TextTrack}s.
13564     */
13565    ;
13566
13567    _proto.clearDisplay = function clearDisplay() {
13568      if (typeof window_1.WebVTT === 'function') {
13569        window_1.WebVTT.processCues(window_1, [], this.el_);
13570      }
13571    }
13572    /**
13573     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
13574     * a {@link Player#fullscreenchange} is fired.
13575     *
13576     * @listens Player#texttrackchange
13577     * @listens Player#fullscreenchange
13578     */
13579    ;
13580
13581    _proto.updateDisplay = function updateDisplay() {
13582      var tracks = this.player_.textTracks();
13583      var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
13584      this.clearDisplay();
13585
13586      if (allowMultipleShowingTracks) {
13587        var showingTracks = [];
13588
13589        for (var _i = 0; _i < tracks.length; ++_i) {
13590          var track = tracks[_i];
13591
13592          if (track.mode !== 'showing') {
13593            continue;
13594          }
13595
13596          showingTracks.push(track);
13597        }
13598
13599        this.updateForTrack(showingTracks);
13600        return;
13601      } //  Track display prioritization model: if multiple tracks are 'showing',
13602      //  display the first 'subtitles' or 'captions' track which is 'showing',
13603      //  otherwise display the first 'descriptions' track which is 'showing'
13604
13605
13606      var descriptionsTrack = null;
13607      var captionsSubtitlesTrack = null;
13608      var i = tracks.length;
13609
13610      while (i--) {
13611        var _track = tracks[i];
13612
13613        if (_track.mode === 'showing') {
13614          if (_track.kind === 'descriptions') {
13615            descriptionsTrack = _track;
13616          } else {
13617            captionsSubtitlesTrack = _track;
13618          }
13619        }
13620      }
13621
13622      if (captionsSubtitlesTrack) {
13623        if (this.getAttribute('aria-live') !== 'off') {
13624          this.setAttribute('aria-live', 'off');
13625        }
13626
13627        this.updateForTrack(captionsSubtitlesTrack);
13628      } else if (descriptionsTrack) {
13629        if (this.getAttribute('aria-live') !== 'assertive') {
13630          this.setAttribute('aria-live', 'assertive');
13631        }
13632
13633        this.updateForTrack(descriptionsTrack);
13634      }
13635    }
13636    /**
13637     * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
13638     *
13639     * @param {TextTrack} track
13640     *        Text track object containing active cues to style.
13641     */
13642    ;
13643
13644    _proto.updateDisplayState = function updateDisplayState(track) {
13645      var overrides = this.player_.textTrackSettings.getValues();
13646      var cues = track.activeCues;
13647      var i = cues.length;
13648
13649      while (i--) {
13650        var cue = cues[i];
13651
13652        if (!cue) {
13653          continue;
13654        }
13655
13656        var cueDiv = cue.displayState;
13657
13658        if (overrides.color) {
13659          cueDiv.firstChild.style.color = overrides.color;
13660        }
13661
13662        if (overrides.textOpacity) {
13663          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
13664        }
13665
13666        if (overrides.backgroundColor) {
13667          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
13668        }
13669
13670        if (overrides.backgroundOpacity) {
13671          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
13672        }
13673
13674        if (overrides.windowColor) {
13675          if (overrides.windowOpacity) {
13676            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
13677          } else {
13678            cueDiv.style.backgroundColor = overrides.windowColor;
13679          }
13680        }
13681
13682        if (overrides.edgeStyle) {
13683          if (overrides.edgeStyle === 'dropshadow') {
13684            cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
13685          } else if (overrides.edgeStyle === 'raised') {
13686            cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
13687          } else if (overrides.edgeStyle === 'depressed') {
13688            cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
13689          } else if (overrides.edgeStyle === 'uniform') {
13690            cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
13691          }
13692        }
13693
13694        if (overrides.fontPercent && overrides.fontPercent !== 1) {
13695          var fontSize = window_1.parseFloat(cueDiv.style.fontSize);
13696          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
13697          cueDiv.style.height = 'auto';
13698          cueDiv.style.top = 'auto';
13699        }
13700
13701        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
13702          if (overrides.fontFamily === 'small-caps') {
13703            cueDiv.firstChild.style.fontVariant = 'small-caps';
13704          } else {
13705            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
13706          }
13707        }
13708      }
13709    }
13710    /**
13711     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
13712     *
13713     * @param {TextTrack|TextTrack[]} tracks
13714     *        Text track object or text track array to be added to the list.
13715     */
13716    ;
13717
13718    _proto.updateForTrack = function updateForTrack(tracks) {
13719      if (!Array.isArray(tracks)) {
13720        tracks = [tracks];
13721      }
13722
13723      if (typeof window_1.WebVTT !== 'function' || tracks.every(function (track) {
13724        return !track.activeCues;
13725      })) {
13726        return;
13727      }
13728
13729      var cues = []; // push all active track cues
13730
13731      for (var i = 0; i < tracks.length; ++i) {
13732        var track = tracks[i];
13733
13734        for (var j = 0; j < track.activeCues.length; ++j) {
13735          cues.push(track.activeCues[j]);
13736        }
13737      } // removes all cues before it processes new ones
13738
13739
13740      window_1.WebVTT.processCues(window_1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
13741
13742      for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
13743        var _track2 = tracks[_i2];
13744
13745        for (var _j = 0; _j < _track2.activeCues.length; ++_j) {
13746          var cueEl = _track2.activeCues[_j].displayState;
13747          addClass(cueEl, 'vjs-text-track-cue');
13748          addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2));
13749        }
13750
13751        if (this.player_.textTrackSettings) {
13752          this.updateDisplayState(_track2);
13753        }
13754      }
13755    };
13756
13757    return TextTrackDisplay;
13758  }(Component$1);
13759
13760  Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
13761
13762  /**
13763   * A loading spinner for use during waiting/loading events.
13764   *
13765   * @extends Component
13766   */
13767
13768  var LoadingSpinner = /*#__PURE__*/function (_Component) {
13769    inheritsLoose(LoadingSpinner, _Component);
13770
13771    function LoadingSpinner() {
13772      return _Component.apply(this, arguments) || this;
13773    }
13774
13775    var _proto = LoadingSpinner.prototype;
13776
13777    /**
13778     * Create the `LoadingSpinner`s DOM element.
13779     *
13780     * @return {Element}
13781     *         The dom element that gets created.
13782     */
13783    _proto.createEl = function createEl$1() {
13784      var isAudio = this.player_.isAudio();
13785      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
13786      var controlText = createEl('span', {
13787        className: 'vjs-control-text',
13788        textContent: this.localize('{1} is loading.', [playerType])
13789      });
13790
13791      var el = _Component.prototype.createEl.call(this, 'div', {
13792        className: 'vjs-loading-spinner',
13793        dir: 'ltr'
13794      });
13795
13796      el.appendChild(controlText);
13797      return el;
13798    };
13799
13800    return LoadingSpinner;
13801  }(Component$1);
13802
13803  Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
13804
13805  /**
13806   * Base class for all buttons.
13807   *
13808   * @extends ClickableComponent
13809   */
13810
13811  var Button = /*#__PURE__*/function (_ClickableComponent) {
13812    inheritsLoose(Button, _ClickableComponent);
13813
13814    function Button() {
13815      return _ClickableComponent.apply(this, arguments) || this;
13816    }
13817
13818    var _proto = Button.prototype;
13819
13820    /**
13821     * Create the `Button`s DOM element.
13822     *
13823     * @param {string} [tag="button"]
13824     *        The element's node type. This argument is IGNORED: no matter what
13825     *        is passed, it will always create a `button` element.
13826     *
13827     * @param {Object} [props={}]
13828     *        An object of properties that should be set on the element.
13829     *
13830     * @param {Object} [attributes={}]
13831     *        An object of attributes that should be set on the element.
13832     *
13833     * @return {Element}
13834     *         The element that gets created.
13835     */
13836    _proto.createEl = function createEl$1(tag, props, attributes) {
13837      if (props === void 0) {
13838        props = {};
13839      }
13840
13841      if (attributes === void 0) {
13842        attributes = {};
13843      }
13844
13845      tag = 'button';
13846      props = assign({
13847        className: this.buildCSSClass()
13848      }, props); // Add attributes for button element
13849
13850      attributes = assign({
13851        // Necessary since the default button type is "submit"
13852        type: 'button'
13853      }, attributes);
13854
13855      var el = createEl(tag, props, attributes);
13856
13857      el.appendChild(createEl('span', {
13858        className: 'vjs-icon-placeholder'
13859      }, {
13860        'aria-hidden': true
13861      }));
13862      this.createControlTextEl(el);
13863      return el;
13864    }
13865    /**
13866     * Add a child `Component` inside of this `Button`.
13867     *
13868     * @param {string|Component} child
13869     *        The name or instance of a child to add.
13870     *
13871     * @param {Object} [options={}]
13872     *        The key/value store of options that will get passed to children of
13873     *        the child.
13874     *
13875     * @return {Component}
13876     *         The `Component` that gets added as a child. When using a string the
13877     *         `Component` will get created by this process.
13878     *
13879     * @deprecated since version 5
13880     */
13881    ;
13882
13883    _proto.addChild = function addChild(child, options) {
13884      if (options === void 0) {
13885        options = {};
13886      }
13887
13888      var className = this.constructor.name;
13889      log$1.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
13890
13891      return Component$1.prototype.addChild.call(this, child, options);
13892    }
13893    /**
13894     * Enable the `Button` element so that it can be activated or clicked. Use this with
13895     * {@link Button#disable}.
13896     */
13897    ;
13898
13899    _proto.enable = function enable() {
13900      _ClickableComponent.prototype.enable.call(this);
13901
13902      this.el_.removeAttribute('disabled');
13903    }
13904    /**
13905     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
13906     * {@link Button#enable}.
13907     */
13908    ;
13909
13910    _proto.disable = function disable() {
13911      _ClickableComponent.prototype.disable.call(this);
13912
13913      this.el_.setAttribute('disabled', 'disabled');
13914    }
13915    /**
13916     * This gets called when a `Button` has focus and `keydown` is triggered via a key
13917     * press.
13918     *
13919     * @param {EventTarget~Event} event
13920     *        The event that caused this function to get called.
13921     *
13922     * @listens keydown
13923     */
13924    ;
13925
13926    _proto.handleKeyDown = function handleKeyDown(event) {
13927      // Ignore Space or Enter key operation, which is handled by the browser for
13928      // a button - though not for its super class, ClickableComponent. Also,
13929      // prevent the event from propagating through the DOM and triggering Player
13930      // hotkeys. We do not preventDefault here because we _want_ the browser to
13931      // handle it.
13932      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
13933        event.stopPropagation();
13934        return;
13935      } // Pass keypress handling up for unsupported keys
13936
13937
13938      _ClickableComponent.prototype.handleKeyDown.call(this, event);
13939    };
13940
13941    return Button;
13942  }(ClickableComponent);
13943
13944  Component$1.registerComponent('Button', Button);
13945
13946  /**
13947   * The initial play button that shows before the video has played. The hiding of the
13948   * `BigPlayButton` get done via CSS and `Player` states.
13949   *
13950   * @extends Button
13951   */
13952
13953  var BigPlayButton = /*#__PURE__*/function (_Button) {
13954    inheritsLoose(BigPlayButton, _Button);
13955
13956    function BigPlayButton(player, options) {
13957      var _this;
13958
13959      _this = _Button.call(this, player, options) || this;
13960      _this.mouseused_ = false;
13961
13962      _this.on('mousedown', function (e) {
13963        return _this.handleMouseDown(e);
13964      });
13965
13966      return _this;
13967    }
13968    /**
13969     * Builds the default DOM `className`.
13970     *
13971     * @return {string}
13972     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
13973     */
13974
13975
13976    var _proto = BigPlayButton.prototype;
13977
13978    _proto.buildCSSClass = function buildCSSClass() {
13979      return 'vjs-big-play-button';
13980    }
13981    /**
13982     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
13983     * for more detailed information on what a click can be.
13984     *
13985     * @param {EventTarget~Event} event
13986     *        The `keydown`, `tap`, or `click` event that caused this function to be
13987     *        called.
13988     *
13989     * @listens tap
13990     * @listens click
13991     */
13992    ;
13993
13994    _proto.handleClick = function handleClick(event) {
13995      var playPromise = this.player_.play(); // exit early if clicked via the mouse
13996
13997      if (this.mouseused_ && event.clientX && event.clientY) {
13998        var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
13999        silencePromise(playPromise);
14000
14001        if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
14002        // calling .focus() on the video element causes the video to go black,
14003        // so we avoid it in that specific case
14004        !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
14005          this.player_.tech(true).focus();
14006        }
14007
14008        return;
14009      }
14010
14011      var cb = this.player_.getChild('controlBar');
14012      var playToggle = cb && cb.getChild('playToggle');
14013
14014      if (!playToggle) {
14015        this.player_.tech(true).focus();
14016        return;
14017      }
14018
14019      var playFocus = function playFocus() {
14020        return playToggle.focus();
14021      };
14022
14023      if (isPromise(playPromise)) {
14024        playPromise.then(playFocus, function () {});
14025      } else {
14026        this.setTimeout(playFocus, 1);
14027      }
14028    };
14029
14030    _proto.handleKeyDown = function handleKeyDown(event) {
14031      this.mouseused_ = false;
14032
14033      _Button.prototype.handleKeyDown.call(this, event);
14034    };
14035
14036    _proto.handleMouseDown = function handleMouseDown(event) {
14037      this.mouseused_ = true;
14038    };
14039
14040    return BigPlayButton;
14041  }(Button);
14042  /**
14043   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
14044   *
14045   * @type {string}
14046   * @private
14047   */
14048
14049
14050  BigPlayButton.prototype.controlText_ = 'Play Video';
14051  Component$1.registerComponent('BigPlayButton', BigPlayButton);
14052
14053  /**
14054   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
14055   * it gets clicked.
14056   *
14057   * @extends Button
14058   */
14059
14060  var CloseButton = /*#__PURE__*/function (_Button) {
14061    inheritsLoose(CloseButton, _Button);
14062
14063    /**
14064    * Creates an instance of the this class.
14065    *
14066    * @param  {Player} player
14067    *         The `Player` that this class should be attached to.
14068    *
14069    * @param  {Object} [options]
14070    *         The key/value store of player options.
14071    */
14072    function CloseButton(player, options) {
14073      var _this;
14074
14075      _this = _Button.call(this, player, options) || this;
14076
14077      _this.controlText(options && options.controlText || _this.localize('Close'));
14078
14079      return _this;
14080    }
14081    /**
14082    * Builds the default DOM `className`.
14083    *
14084    * @return {string}
14085    *         The DOM `className` for this object.
14086    */
14087
14088
14089    var _proto = CloseButton.prototype;
14090
14091    _proto.buildCSSClass = function buildCSSClass() {
14092      return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
14093    }
14094    /**
14095     * This gets called when a `CloseButton` gets clicked. See
14096     * {@link ClickableComponent#handleClick} for more information on when
14097     * this will be triggered
14098     *
14099     * @param {EventTarget~Event} event
14100     *        The `keydown`, `tap`, or `click` event that caused this function to be
14101     *        called.
14102     *
14103     * @listens tap
14104     * @listens click
14105     * @fires CloseButton#close
14106     */
14107    ;
14108
14109    _proto.handleClick = function handleClick(event) {
14110      /**
14111       * Triggered when the a `CloseButton` is clicked.
14112       *
14113       * @event CloseButton#close
14114       * @type {EventTarget~Event}
14115       *
14116       * @property {boolean} [bubbles=false]
14117       *           set to false so that the close event does not
14118       *           bubble up to parents if there is no listener
14119       */
14120      this.trigger({
14121        type: 'close',
14122        bubbles: false
14123      });
14124    }
14125    /**
14126     * Event handler that is called when a `CloseButton` receives a
14127     * `keydown` event.
14128     *
14129     * By default, if the key is Esc, it will trigger a `click` event.
14130     *
14131     * @param {EventTarget~Event} event
14132     *        The `keydown` event that caused this function to be called.
14133     *
14134     * @listens keydown
14135     */
14136    ;
14137
14138    _proto.handleKeyDown = function handleKeyDown(event) {
14139      // Esc button will trigger `click` event
14140      if (keycode.isEventKey(event, 'Esc')) {
14141        event.preventDefault();
14142        event.stopPropagation();
14143        this.trigger('click');
14144      } else {
14145        // Pass keypress handling up for unsupported keys
14146        _Button.prototype.handleKeyDown.call(this, event);
14147      }
14148    };
14149
14150    return CloseButton;
14151  }(Button);
14152
14153  Component$1.registerComponent('CloseButton', CloseButton);
14154
14155  /**
14156   * Button to toggle between play and pause.
14157   *
14158   * @extends Button
14159   */
14160
14161  var PlayToggle = /*#__PURE__*/function (_Button) {
14162    inheritsLoose(PlayToggle, _Button);
14163
14164    /**
14165     * Creates an instance of this class.
14166     *
14167     * @param {Player} player
14168     *        The `Player` that this class should be attached to.
14169     *
14170     * @param {Object} [options={}]
14171     *        The key/value store of player options.
14172     */
14173    function PlayToggle(player, options) {
14174      var _this;
14175
14176      if (options === void 0) {
14177        options = {};
14178      }
14179
14180      _this = _Button.call(this, player, options) || this; // show or hide replay icon
14181
14182      options.replay = options.replay === undefined || options.replay;
14183
14184      _this.on(player, 'play', function (e) {
14185        return _this.handlePlay(e);
14186      });
14187
14188      _this.on(player, 'pause', function (e) {
14189        return _this.handlePause(e);
14190      });
14191
14192      if (options.replay) {
14193        _this.on(player, 'ended', function (e) {
14194          return _this.handleEnded(e);
14195        });
14196      }
14197
14198      return _this;
14199    }
14200    /**
14201     * Builds the default DOM `className`.
14202     *
14203     * @return {string}
14204     *         The DOM `className` for this object.
14205     */
14206
14207
14208    var _proto = PlayToggle.prototype;
14209
14210    _proto.buildCSSClass = function buildCSSClass() {
14211      return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
14212    }
14213    /**
14214     * This gets called when an `PlayToggle` is "clicked". See
14215     * {@link ClickableComponent} for more detailed information on what a click can be.
14216     *
14217     * @param {EventTarget~Event} [event]
14218     *        The `keydown`, `tap`, or `click` event that caused this function to be
14219     *        called.
14220     *
14221     * @listens tap
14222     * @listens click
14223     */
14224    ;
14225
14226    _proto.handleClick = function handleClick(event) {
14227      if (this.player_.paused()) {
14228        silencePromise(this.player_.play());
14229      } else {
14230        this.player_.pause();
14231      }
14232    }
14233    /**
14234     * This gets called once after the video has ended and the user seeks so that
14235     * we can change the replay button back to a play button.
14236     *
14237     * @param {EventTarget~Event} [event]
14238     *        The event that caused this function to run.
14239     *
14240     * @listens Player#seeked
14241     */
14242    ;
14243
14244    _proto.handleSeeked = function handleSeeked(event) {
14245      this.removeClass('vjs-ended');
14246
14247      if (this.player_.paused()) {
14248        this.handlePause(event);
14249      } else {
14250        this.handlePlay(event);
14251      }
14252    }
14253    /**
14254     * Add the vjs-playing class to the element so it can change appearance.
14255     *
14256     * @param {EventTarget~Event} [event]
14257     *        The event that caused this function to run.
14258     *
14259     * @listens Player#play
14260     */
14261    ;
14262
14263    _proto.handlePlay = function handlePlay(event) {
14264      this.removeClass('vjs-ended');
14265      this.removeClass('vjs-paused');
14266      this.addClass('vjs-playing'); // change the button text to "Pause"
14267
14268      this.controlText('Pause');
14269    }
14270    /**
14271     * Add the vjs-paused class to the element so it can change appearance.
14272     *
14273     * @param {EventTarget~Event} [event]
14274     *        The event that caused this function to run.
14275     *
14276     * @listens Player#pause
14277     */
14278    ;
14279
14280    _proto.handlePause = function handlePause(event) {
14281      this.removeClass('vjs-playing');
14282      this.addClass('vjs-paused'); // change the button text to "Play"
14283
14284      this.controlText('Play');
14285    }
14286    /**
14287     * Add the vjs-ended class to the element so it can change appearance
14288     *
14289     * @param {EventTarget~Event} [event]
14290     *        The event that caused this function to run.
14291     *
14292     * @listens Player#ended
14293     */
14294    ;
14295
14296    _proto.handleEnded = function handleEnded(event) {
14297      var _this2 = this;
14298
14299      this.removeClass('vjs-playing');
14300      this.addClass('vjs-ended'); // change the button text to "Replay"
14301
14302      this.controlText('Replay'); // on the next seek remove the replay button
14303
14304      this.one(this.player_, 'seeked', function (e) {
14305        return _this2.handleSeeked(e);
14306      });
14307    };
14308
14309    return PlayToggle;
14310  }(Button);
14311  /**
14312   * The text that should display over the `PlayToggle`s controls. Added for localization.
14313   *
14314   * @type {string}
14315   * @private
14316   */
14317
14318
14319  PlayToggle.prototype.controlText_ = 'Play';
14320  Component$1.registerComponent('PlayToggle', PlayToggle);
14321
14322  /**
14323   * @file format-time.js
14324   * @module format-time
14325   */
14326
14327  /**
14328   * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
14329   * seconds) will force a number of leading zeros to cover the length of the
14330   * guide.
14331   *
14332   * @private
14333   * @param  {number} seconds
14334   *         Number of seconds to be turned into a string
14335   *
14336   * @param  {number} guide
14337   *         Number (in seconds) to model the string after
14338   *
14339   * @return {string}
14340   *         Time formatted as H:MM:SS or M:SS
14341   */
14342  var defaultImplementation = function defaultImplementation(seconds, guide) {
14343    seconds = seconds < 0 ? 0 : seconds;
14344    var s = Math.floor(seconds % 60);
14345    var m = Math.floor(seconds / 60 % 60);
14346    var h = Math.floor(seconds / 3600);
14347    var gm = Math.floor(guide / 60 % 60);
14348    var gh = Math.floor(guide / 3600); // handle invalid times
14349
14350    if (isNaN(seconds) || seconds === Infinity) {
14351      // '-' is false for all relational operators (e.g. <, >
14351=) so this setting
14352      // will add the minimum number of fields specified by the guide
14353      h = m = s = '-';
14354    } // Check if we need to show hours
14355
14356
14357    h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
14358    // Always show at least one digit of minutes.
14359
14360    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
14361
14362    s = s < 10 ? '0' + s : s;
14363    return h + m + s;
14364  }; // Internal pointer to the current implementation.
14365
14366
14367  var implementation = defaultImplementation;
14368  /**
14369   * Replaces the default formatTime implementation with a custom implementation.
14370   *
14371   * @param {Function} customImplementation
14372   *        A function which will be used in place of the default formatTime
14373   *        implementation. Will receive the current time in seconds and the
14374   *        guide (in seconds) as arguments.
14375   */
14376
14377  function setFormatTime(customImplementation) {
14378    implementation = customImplementation;
14379  }
14380  /**
14381   * Resets formatTime to the default implementation.
14382   */
14383
14384  function resetFormatTime() {
14385    implementation = defaultImplementation;
14386  }
14387  /**
14388   * Delegates to either the default time formatting function or a custom
14389   * function supplied via `setFormatTime`.
14390   *
14391   * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
14392   * guide (in seconds) will force a number of leading zeros to cover the
14393   * length of the guide.
14394   *
14395   * @static
14396   * @example  formatTime(125, 600) === "02:05"
14397   * @param    {number} seconds
14398   *           Number of seconds to be turned into a string
14399   *
14400   * @param    {number} guide
14401   *           Number (in seconds) to model the string after
14402   *
14403   * @return   {string}
14404   *           Time formatted as H:MM:SS or M:SS
14405   */
14406
14407  function formatTime(seconds, guide) {
14408    if (guide === void 0) {
14409      guide = seconds;
14410    }
14411
14412    return implementation(seconds, guide);
14413  }
14414
14415  /**
14416   * Displays time information about the video
14417   *
14418   * @extends Component
14419   */
14420
14421  var TimeDisplay = /*#__PURE__*/function (_Component) {
14422    inheritsLoose(TimeDisplay, _Component);
14423
14424    /**
14425     * Creates an instance of this class.
14426     *
14427     * @param {Player} player
14428     *        The `Player` that this class should be attached to.
14429     *
14430     * @param {Object} [options]
14431     *        The key/value store of player options.
14432     */
14433    function TimeDisplay(player, options) {
14434      var _this;
14435
14436      _this = _Component.call(this, player, options) || this;
14437
14438      _this.on(player, ['timeupdate', 'ended'], function (e) {
14439        return _this.updateContent(e);
14440      });
14441
14442      _this.updateTextNode_();
14443
14444      return _this;
14445    }
14446    /**
14447     * Create the `Component`'s DOM element
14448     *
14449     * @return {Element}
14450     *         The element that was created.
14451     */
14452
14453
14454    var _proto = TimeDisplay.prototype;
14455
14456    _proto.createEl = function createEl$1() {
14457      var className = this.buildCSSClass();
14458
14459      var el = _Component.prototype.createEl.call(this, 'div', {
14460        className: className + " vjs-time-control vjs-control"
14461      });
14462
14463      var span = createEl('span', {
14464        className: 'vjs-control-text',
14465        textContent: this.localize(this.labelText_) + "\xA0"
14466      }, {
14467        role: 'presentation'
14468      });
14469      el.appendChild(span);
14470      this.contentEl_ = createEl('span', {
14471        className: className + "-display"
14472      }, {
14473        // tell screen readers not to automatically read the time as it changes
14474        'aria-live': 'off',
14475        // span elements have no implicit role, but some screen readers (notably VoiceOver)
14476        // treat them as a break between items in the DOM when using arrow keys
14477        // (or left-to-right swipes on iOS) to read contents of a page. Using
14478        // role='presentation' causes VoiceOver to NOT treat this span as a break.
14479        'role': 'presentation'
14480      });
14481      el.appendChild(this.contentEl_);
14482      return el;
14483    };
14484
14485    _proto.dispose = function dispose() {
14486      this.contentEl_ = null;
14487      this.textNode_ = null;
14488
14489      _Component.prototype.dispose.call(this);
14490    }
14491    /**
14492     * Updates the time display text node with a new time
14493     *
14494     * @param {number} [time=0] the time to update to
14495     *
14496     * @private
14497     */
14498    ;
14499
14500    _proto.updateTextNode_ = function updateTextNode_(time) {
14501      var _this2 = this;
14502
14503      if (time === void 0) {
14504        time = 0;
14505      }
14506
14507      time = formatTime(time);
14508
14509      if (this.formattedTime_ === time) {
14510        return;
14511      }
14512
14513      this.formattedTime_ = time;
14514      this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', function () {
14515        if (!_this2.contentEl_) {
14516          return;
14517        }
14518
14519        var oldNode = _this2.textNode_;
14520
14521        if (oldNode && _this2.contentEl_.firstChild !== oldNode) {
14522          oldNode = null;
14523          log$1.warn('TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.');
14524        }
14525
14526        _this2.textNode_ = document_1.createTextNode(_this2.formattedTime_);
14527
14528        if (!_this2.textNode_) {
14529          return;
14530        }
14531
14532        if (oldNode) {
14533          _this2.contentEl_.replaceChild(_this2.textNode_, oldNode);
14534        } else {
14535          _this2.contentEl_.appendChild(_this2.textNode_);
14536        }
14537      });
14538    }
14539    /**
14540     * To be filled out in the child class, should update the displayed time
14541     * in accordance with the fact that the current time has changed.
14542     *
14543     * @param {EventTarget~Event} [event]
14544     *        The `timeupdate`  event that caused this to run.
14545     *
14546     * @listens Player#timeupdate
14547     */
14548    ;
14549
14550    _proto.updateContent = function updateContent(event) {};
14551
14552    return TimeDisplay;
14553  }(Component$1);
14554  /**
14555   * The text that is added to the `TimeDisplay` for screen reader users.
14556   *
14557   * @type {string}
14558   * @private
14559   */
14560
14561
14562  TimeDisplay.prototype.labelText_ = 'Time';
14563  /**
14564   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
14565   *
14566   * @type {string}
14567   * @private
14568   *
14569   * @deprecated in v7; controlText_ is not used in non-active display Components
14570   */
14571
14572  TimeDisplay.prototype.controlText_ = 'Time';
14573  Component$1.registerComponent('TimeDisplay', TimeDisplay);
14574
14575  /**
14576   * Displays the current time
14577   *
14578   * @extends Component
14579   */
14580
14581  var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
14582    inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
14583
14584    function CurrentTimeDisplay() {
14585      return _TimeDisplay.apply(this, arguments) || this;
14586    }
14587
14588    var _proto = CurrentTimeDisplay.prototype;
14589
14590    /**
14591     * Builds the default DOM `className`.
14592     *
14593     * @return {string}
14594     *         The DOM `className` for this object.
14595     */
14596    _proto.buildCSSClass = function buildCSSClass() {
14597      return 'vjs-current-time';
14598    }
14599    /**
14600     * Update current time display
14601     *
14602     * @param {EventTarget~Event} [event]
14603     *        The `timeupdate` event that caused this function to run.
14604     *
14605     * @listens Player#timeupdate
14606     */
14607    ;
14608
14609    _proto.updateContent = function updateContent(event) {
14610      // Allows for smooth scrubbing, when player can't keep up.
14611      var time;
14612
14613      if (this.player_.ended()) {
14614        time = this.player_.duration();
14615      } else {
14616        time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14617      }
14618
14619      this.updateTextNode_(time);
14620    };
14621
14622    return CurrentTimeDisplay;
14623  }(TimeDisplay);
14624  /**
14625   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
14626   *
14627   * @type {string}
14628   * @private
14629   */
14630
14631
14632  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
14633  /**
14634   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
14635   *
14636   * @type {string}
14637   * @private
14638   *
14639   * @deprecated in v7; controlText_ is not used in non-active display Components
14640   */
14641
14642  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
14643  Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
14644
14645  /**
14646   * Displays the duration
14647   *
14648   * @extends Component
14649   */
14650
14651  var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) {
14652    inheritsLoose(DurationDisplay, _TimeDisplay);
14653
14654    /**
14655     * Creates an instance of this class.
14656     *
14657     * @param {Player} player
14658     *        The `Player` that this class should be attached to.
14659     *
14660     * @param {Object} [options]
14661     *        The key/value store of player options.
14662     */
14663    function DurationDisplay(player, options) {
14664      var _this;
14665
14666      _this = _TimeDisplay.call(this, player, options) || this;
14667
14668      var updateContent = function updateContent(e) {
14669        return _this.updateContent(e);
14670      }; // we do not want to/need to throttle duration changes,
14671      // as they should always display the changed duration as
14672      // it has changed
14673
14674
14675      _this.on(player, 'durationchange', updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
14676      // but the durationchange on the user agent will not fire.
14677      // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
14678
14679
14680      _this.on(player, 'loadstart', updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
14681      // listeners could have broken dependent applications/libraries. These
14682      // can likely be removed for 7.0.
14683
14684
14685      _this.on(player, 'loadedmetadata', updateContent);
14686
14687      return _this;
14688    }
14689    /**
14690     * Builds the default DOM `className`.
14691     *
14692     * @return {string}
14693     *         The DOM `className` for this object.
14694     */
14695
14696
14697    var _proto = DurationDisplay.prototype;
14698
14699    _proto.buildCSSClass = function buildCSSClass() {
14700      return 'vjs-duration';
14701    }
14702    /**
14703     * Update duration time display.
14704     *
14705     * @param {EventTarget~Event} [event]
14706     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
14707     *        this function to be called.
14708     *
14709     * @listens Player#durationchange
14710     * @listens Player#timeupdate
14711     * @listens Player#loadedmetadata
14712     */
14713    ;
14714
14715    _proto.updateContent = function updateContent(event) {
14716      var duration = this.player_.duration();
14717      this.updateTextNode_(duration);
14718    };
14719
14720    return DurationDisplay;
14721  }(TimeDisplay);
14722  /**
14723   * The text that is added to the `DurationDisplay` for screen reader users.
14724   *
14725   * @type {string}
14726   * @private
14727   */
14728
14729
14730  DurationDisplay.prototype.labelText_ = 'Duration';
14731  /**
14732   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
14733   *
14734   * @type {string}
14735   * @private
14736   *
14737   * @deprecated in v7; controlText_ is not used in non-active display Components
14738   */
14739
14740  DurationDisplay.prototype.controlText_ = 'Duration';
14741  Component$1.registerComponent('DurationDisplay', DurationDisplay);
14742
14743  /**
14744   * The separator between the current time and duration.
14745   * Can be hidden if it's not needed in the design.
14746   *
14747   * @extends Component
14748   */
14749
14750  var TimeDivider = /*#__PURE__*/function (_Component) {
14751    inheritsLoose(TimeDivider, _Component);
14752
14753    function TimeDivider() {
14754      return _Component.apply(this, arguments) || this;
14755    }
14756
14757    var _proto = TimeDivider.prototype;
14758
14759    /**
14760     * Create the component's DOM element
14761     *
14762     * @return {Element}
14763     *         The element that was created.
14764     */
14765    _proto.createEl = function createEl() {
14766      var el = _Component.prototype.createEl.call(this, 'div', {
14767        className: 'vjs-time-control vjs-time-divider'
14768      }, {
14769        // this element and its contents can be hidden from assistive techs since
14770        // it is made extraneous by the announcement of the control text
14771        // for the current time and duration displays
14772        'aria-hidden': true
14773      });
14774
14775      var div = _Component.prototype.createEl.call(this, 'div');
14776
14777      var span = _Component.prototype.createEl.call(this, 'span', {
14778        textContent: '/'
14779      });
14780
14781      div.appendChild(span);
14782      el.appendChild(div);
14783      return el;
14784    };
14785
14786    return TimeDivider;
14787  }(Component$1);
14788
14789  Component$1.registerComponent('TimeDivider', TimeDivider);
14790
14791  /**
14792   * Displays the time left in the video
14793   *
14794   * @extends Component
14795   */
14796
14797  var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
14798    inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
14799
14800    /**
14801     * Creates an instance of this class.
14802     *
14803     * @param {Player} player
14804     *        The `Player` that this class should be attached to.
14805     *
14806     * @param {Object} [options]
14807     *        The key/value store of player options.
14808     */
14809    function RemainingTimeDisplay(player, options) {
14810      var _this;
14811
14812      _this = _TimeDisplay.call(this, player, options) || this;
14813
14814      _this.on(player, 'durationchange', function (e) {
14815        return _this.updateContent(e);
14816      });
14817
14818      return _this;
14819    }
14820    /**
14821     * Builds the default DOM `className`.
14822     *
14823     * @return {string}
14824     *         The DOM `className` for this object.
14825     */
14826
14827
14828    var _proto = RemainingTimeDisplay.prototype;
14829
14830    _proto.buildCSSClass = function buildCSSClass() {
14831      return 'vjs-remaining-time';
14832    }
14833    /**
14834     * Create the `Component`'s DOM element with the "minus" characted prepend to the time
14835     *
14836     * @return {Element}
14837     *         The element that was created.
14838     */
14839    ;
14840
14841    _proto.createEl = function createEl$1() {
14842      var el = _TimeDisplay.prototype.createEl.call(this);
14843
14844      el.insertBefore(createEl('span', {}, {
14845        'aria-hidden': true
14846      }, '-'), this.contentEl_);
14847      return el;
14848    }
14849    /**
14850     * Update remaining time display.
14851     *
14852     * @param {EventTarget~Event} [event]
14853     *        The `timeupdate` or `durationchange` event that caused this to run.
14854     *
14855     * @listens Player#timeupdate
14856     * @listens Player#durationchange
14857     */
14858    ;
14859
14860    _proto.updateContent = function updateContent(event) {
14861      if (typeof this.player_.duration() !== 'number') {
14862        return;
14863      }
14864
14865      var time; // @deprecated We should only use remainingTimeDisplay
14866      // as of video.js 7
14867
14868      if (this.player_.ended()) {
14869        time = 0;
14870      } else if (this.player_.remainingTimeDisplay) {
14871        time = this.player_.remainingTimeDisplay();
14872      } else {
14873        time = this.player_.remainingTime();
14874      }
14875
14876      this.updateTextNode_(time);
14877    };
14878
14879    return RemainingTimeDisplay;
14880  }(TimeDisplay);
14881  /**
14882   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
14883   *
14884   * @type {string}
14885   * @private
14886   */
14887
14888
14889  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
14890  /**
14891   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
14892   *
14893   * @type {string}
14894   * @private
14895   *
14896   * @deprecated in v7; controlText_ is not used in non-active display Components
14897   */
14898
14899  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
14900  Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
14901
14902  /**
14903   * Displays the live indicator when duration is Infinity.
14904   *
14905   * @extends Component
14906   */
14907
14908  var LiveDisplay = /*#__PURE__*/function (_Component) {
14909    inheritsLoose(LiveDisplay, _Component);
14910
14911    /**
14912     * Creates an instance of this class.
14913     *
14914     * @param {Player} player
14915     *        The `Player` that this class should be attached to.
14916     *
14917     * @param {Object} [options]
14918     *        The key/value store of player options.
14919     */
14920    function LiveDisplay(player, options) {
14921      var _this;
14922
14923      _this = _Component.call(this, player, options) || this;
14924
14925      _this.updateShowing();
14926
14927      _this.on(_this.player(), 'durationchange', function (e) {
14928        return _this.updateShowing(e);
14929      });
14930
14931      return _this;
14932    }
14933    /**
14934     * Create the `Component`'s DOM element
14935     *
14936     * @return {Element}
14937     *         The element that was created.
14938     */
14939
14940
14941    var _proto = LiveDisplay.prototype;
14942
14943    _proto.createEl = function createEl$1() {
14944      var el = _Component.prototype.createEl.call(this, 'div', {
14945        className: 'vjs-live-control vjs-control'
14946      });
14947
14948      this.contentEl_ = createEl('div', {
14949        className: 'vjs-live-display'
14950      }, {
14951        'aria-live': 'off'
14952      });
14953      this.contentEl_.appendChild(createEl('span', {
14954        className: 'vjs-control-text',
14955        textContent: this.localize('Stream Type') + "\xA0"
14956      }));
14957      this.contentEl_.appendChild(document_1.createTextNode(this.localize('LIVE')));
14958      el.appendChild(this.contentEl_);
14959      return el;
14960    };
14961
14962    _proto.dispose = function dispose() {
14963      this.contentEl_ = null;
14964
14965      _Component.prototype.dispose.call(this);
14966    }
14967    /**
14968     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
14969     * it accordingly
14970     *
14971     * @param {EventTarget~Event} [event]
14972     *        The {@link Player#durationchange} event that caused this function to run.
14973     *
14974     * @listens Player#durationchange
14975     */
14976    ;
14977
14978    _proto.updateShowing = function updateShowing(event) {
14979      if (this.player().duration() === Infinity) {
14980        this.show();
14981      } else {
14982        this.hide();
14983      }
14984    };
14985
14986    return LiveDisplay;
14987  }(Component$1);
14988
14989  Component$1.registerComponent('LiveDisplay', LiveDisplay);
14990
14991  /**
14992   * Displays the live indicator when duration is Infinity.
14993   *
14994   * @extends Component
14995   */
14996
14997  var SeekToLive = /*#__PURE__*/function (_Button) {
14998    inheritsLoose(SeekToLive, _Button);
14999
15000    /**
15001     * Creates an instance of this class.
15002     *
15003     * @param {Player} player
15004     *        The `Player` that this class should be attached to.
15005     *
15006     * @param {Object} [options]
15007     *        The key/value store of player options.
15008     */
15009    function SeekToLive(player, options) {
15010      var _this;
15011
15012      _this = _Button.call(this, player, options) || this;
15013
15014      _this.updateLiveEdgeStatus();
15015
15016      if (_this.player_.liveTracker) {
15017        _this.updateLiveEdgeStatusHandler_ = function (e) {
15018          return _this.updateLiveEdgeStatus(e);
15019        };
15020
15021        _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatusHandler_);
15022      }
15023
15024      return _this;
15025    }
15026    /**
15027     * Create the `Component`'s DOM element
15028     *
15029     * @return {Element}
15030     *         The element that was created.
15031     */
15032
15033
15034    var _proto = SeekToLive.prototype;
15035
15036    _proto.createEl = function createEl$1() {
15037      var el = _Button.prototype.createEl.call(this, 'button', {
15038        className: 'vjs-seek-to-live-control vjs-control'
15039      });
15040
15041      this.textEl_ = createEl('span', {
15042        className: 'vjs-seek-to-live-text',
15043        textContent: this.localize('LIVE')
15044      }, {
15045        'aria-hidden': 'true'
15046      });
15047      el.appendChild(this.textEl_);
15048      return el;
15049    }
15050    /**
15051     * Update the state of this button if we are at the live edge
15052     * or not
15053     */
15054    ;
15055
15056    _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() {
15057      // default to live edge
15058      if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
15059        this.setAttribute('aria-disabled', true);
15060        this.addClass('vjs-at-live-edge');
15061        this.controlText('Seek to live, currently playing live');
15062      } else {
15063        this.setAttribute('aria-disabled', false);
15064        this.removeClass('vjs-at-live-edge');
15065        this.controlText('Seek to live, currently behind live');
15066      }
15067    }
15068    /**
15069     * On click bring us as near to the live point as possible.
15070     * This requires that we wait for the next `live-seekable-change`
15071     * event which will happen every segment length seconds.
15072     */
15073    ;
15074
15075    _proto.handleClick = function handleClick() {
15076      this.player_.liveTracker.seekToLiveEdge();
15077    }
15078    /**
15079     * Dispose of the element and stop tracking
15080     */
15081    ;
15082
15083    _proto.dispose = function dispose() {
15084      if (this.player_.liveTracker) {
15085        this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
15086      }
15087
15088      this.textEl_ = null;
15089
15090      _Button.prototype.dispose.call(this);
15091    };
15092
15093    return SeekToLive;
15094  }(Button);
15095
15096  SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
15097  Component$1.registerComponent('SeekToLive', SeekToLive);
15098
15099  /**
15100   * Keep a number between a min and a max value
15101   *
15102   * @param {number} number
15103   *        The number to clamp
15104   *
15105   * @param {number} min
15106   *        The minimum value
15107   * @param {number} max
15108   *        The maximum value
15109   *
15110   * @return {number}
15111   *         the clamped number
15112   */
15113  var clamp = function clamp(number, min, max) {
15114    number = Number(number);
15115    return Math.min(max, Math.max(min, isNaN(number) ? min : number));
15116  };
15117
15118  /**
15119   * The base functionality for a slider. Can be vertical or horizontal.
15120   * For instance the volume bar or the seek bar on a video is a slider.
15121   *
15122   * @extends Component
15123   */
15124
15125  var Slider = /*#__PURE__*/function (_Component) {
15126    inheritsLoose(Slider, _Component);
15127
15128    /**
15129    * Create an instance of this class
15130    *
15131    * @param {Player} player
15132    *        The `Player` that this class should be attached to.
15133    *
15134    * @param {Object} [options]
15135    *        The key/value store of player options.
15136    */
15137    function Slider(player, options) {
15138      var _this;
15139
15140      _this = _Component.call(this, player, options) || this;
15141
15142      _this.handleMouseDown_ = function (e) {
15143        return _this.handleMouseDown(e);
15144      };
15145
15146      _this.handleMouseUp_ = function (e) {
15147        return _this.handleMouseUp(e);
15148      };
15149
15150      _this.handleKeyDown_ = function (e) {
15151        return _this.handleKeyDown(e);
15152      };
15153
15154      _this.handleClick_ = function (e) {
15155        return _this.handleClick(e);
15156      };
15157
15158      _this.handleMouseMove_ = function (e) {
15159        return _this.handleMouseMove(e);
15160      };
15161
15162      _this.update_ = function (e) {
15163        return _this.update(e);
15164      }; // Set property names to bar to match with the child Slider class is looking for
15165
15166
15167      _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
15168
15169      _this.vertical(!!_this.options_.vertical);
15170
15171      _this.enable();
15172
15173      return _this;
15174    }
15175    /**
15176     * Are controls are currently enabled for this slider or not.
15177     *
15178     * @return {boolean}
15179     *         true if controls are enabled, false otherwise
15180     */
15181
15182
15183    var _proto = Slider.prototype;
15184
15185    _proto.enabled = function enabled() {
15186      return this.enabled_;
15187    }
15188    /**
15189     * Enable controls for this slider if they are disabled
15190     */
15191    ;
15192
15193    _proto.enable = function enable() {
15194      if (this.enabled()) {
15195        return;
15196      }
15197
15198      this.on('mousedown', this.handleMouseDown_);
15199      this.on('touchstart', this.handleMouseDown_);
15200      this.on('keydown', this.handleKeyDown_);
15201      this.on('click', this.handleClick_); // TODO: deprecated, controlsvisible does not seem to be fired
15202
15203      this.on(this.player_, 'controlsvisible', this.update);
15204
15205      if (this.playerEvent) {
15206        this.on(this.player_, this.playerEvent, this.update);
15207      }
15208
15209      this.removeClass('disabled');
15210      this.setAttribute('tabindex', 0);
15211      this.enabled_ = true;
15212    }
15213    /**
15214     * Disable controls for this slider if they are enabled
15215     */
15216    ;
15217
15218    _proto.disable = function disable() {
15219      if (!this.enabled()) {
15220        return;
15221      }
15222
15223      var doc = this.bar.el_.ownerDocument;
15224      this.off('mousedown', this.handleMouseDown_);
15225      this.off('touchstart', this.handleMouseDown_);
15226      this.off('keydown', this.handleKeyDown_);
15227      this.off('click', this.handleClick_);
15228      this.off(this.player_, 'controlsvisible', this.update_);
15229      this.off(doc, 'mousemove', this.handleMouseMove_);
15230      this.off(doc, 'mouseup', this.handleMouseUp_);
15231      this.off(doc, 'touchmove', this.handleMouseMove_);
15232      this.off(doc, 'touchend', this.handleMouseUp_);
15233      this.removeAttribute('tabindex');
15234      this.addClass('disabled');
15235
15236      if (this.playerEvent) {
15237        this.off(this.player_, this.playerEvent, this.update);
15238      }
15239
15240      this.enabled_ = false;
15241    }
15242    /**
15243     * Create the `Slider`s DOM element.
15244     *
15245     * @param {string} type
15246     *        Type of element to create.
15247     *
15248     * @param {Object} [props={}]
15249     *        List of properties in Object form.
15250     *
15251     * @param {Object} [attributes={}]
15252     *        list of attributes in Object form.
15253     *
15254     * @return {Element}
15255     *         The element that gets created.
15256     */
15257    ;
15258
15259    _proto.createEl = function createEl(type, props, attributes) {
15260      if (props === void 0) {
15261        props = {};
15262      }
15263
15264      if (attributes === void 0) {
15265        attributes = {};
15266      }
15267
15268      // Add the slider element class to all sub classes
15269      props.className = props.className + ' vjs-slider';
15270      props = assign({
15271        tabIndex: 0
15272      }, props);
15273      attributes = assign({
15274        'role': 'slider',
15275        'aria-valuenow': 0,
15276        'aria-valuemin': 0,
15277        'aria-valuemax': 100,
15278        'tabIndex': 0
15279      }, attributes);
15280      return _Component.prototype.createEl.call(this, type, props, attributes);
15281    }
15282    /**
15283     * Handle `mousedown` or `touchstart` events on the `Slider`.
15284     *
15285     * @param {EventTarget~Event} event
15286     *        `mousedown` or `touchstart` event that triggered this function
15287     *
15288     * @listens mousedown
15289     * @listens touchstart
15290     * @fires Slider#slideractive
15291     */
15292    ;
15293
15294    _proto.handleMouseDown = function handleMouseDown(event) {
15295      var doc = this.bar.el_.ownerDocument;
15296
15297      if (event.type === 'mousedown') {
15298        event.preventDefault();
15299      } // Do not call preventDefault() on touchstart in Chrome
15300      // to avoid console warnings. Use a 'touch-action: none' style
15301      // instead to prevent unintented scrolling.
15302      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
15303
15304
15305      if (event.type === 'touchstart' && !IS_CHROME) {
15306        event.preventDefault();
15307      }
15308
15309      blockTextSelection();
15310      this.addClass('vjs-sliding');
15311      /**
15312       * Triggered when the slider is in an active state
15313       *
15314       * @event Slider#slideractive
15315       * @type {EventTarget~Event}
15316       */
15317
15318      this.trigger('slideractive');
15319      this.on(doc, 'mousemove', this.handleMouseMove_);
15320      this.on(doc, 'mouseup', this.handleMouseUp_);
15321      this.on(doc, 'touchmove', this.handleMouseMove_);
15322      this.on(doc, 'touchend', this.handleMouseUp_);
15323      this.handleMouseMove(event);
15324    }
15325    /**
15326     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
15327     * The `mousemove` and `touchmove` events will only only trigger this function during
15328     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
15329     * {@link Slider#handleMouseUp}.
15330     *
15331     * @param {EventTarget~Event} event
15332     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
15333     *        this function
15334     *
15335     * @listens mousemove
15336     * @listens touchmove
15337     */
15338    ;
15339
15340    _proto.handleMouseMove = function handleMouseMove(event) {}
15341    /**
15342     * Handle `mouseup` or `touchend` events on the `Slider`.
15343     *
15344     * @param {EventTarget~Event} event
15345     *        `mouseup` or `touchend` event that triggered this function.
15346     *
15347     * @listens touchend
15348     * @listens mouseup
15349     * @fires Slider#sliderinactive
15350     */
15351    ;
15352
15353    _proto.handleMouseUp = function handleMouseUp() {
15354      var doc = this.bar.el_.ownerDocument;
15355      unblockTextSelection();
15356      this.removeClass('vjs-sliding');
15357      /**
15358       * Triggered when the slider is no longer in an active state.
15359       *
15360       * @event Slider#sliderinactive
15361       * @type {EventTarget~Event}
15362       */
15363
15364      this.trigger('sliderinactive');
15365      this.off(doc, 'mousemove', this.handleMouseMove_);
15366      this.off(doc, 'mouseup', this.handleMouseUp_);
15367      this.off(doc, 'touchmove', this.handleMouseMove_);
15368      this.off(doc, 'touchend', this.handleMouseUp_);
15369      this.update();
15370    }
15371    /**
15372     * Update the progress bar of the `Slider`.
15373     *
15374     * @return {number}
15375     *          The percentage of progress the progress bar represents as a
15376     *          number from 0 to 1.
15377     */
15378    ;
15379
15380    _proto.update = function update() {
15381      var _this2 = this;
15382
15383      // In VolumeBar init we have a setTimeout for update that pops and update
15384      // to the end of the execution stack. The player is destroyed before then
15385      // update will cause an error
15386      // If there's no bar...
15387      if (!this.el_ || !this.bar) {
15388        return;
15389      } // clamp progress between 0 and 1
15390      // and only round to four decimal places, as we round to two below
15391
15392
15393      var progress = this.getProgress();
15394
15395      if (progress === this.progress_) {
15396        return progress;
15397      }
15398
15399      this.progress_ = progress;
15400      this.requestNamedAnimationFrame('Slider#update', function () {
15401        // Set the new bar width or height
15402        var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value
15403
15404        _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
15405      });
15406      return progress;
15407    }
15408    /**
15409     * Get the percentage of the bar that should be filled
15410     * but clamped and rounded.
15411     *
15412     * @return {number}
15413     *         percentage filled that the slider is
15414     */
15415    ;
15416
15417    _proto.getProgress = function getProgress() {
15418      return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
15419    }
15420    /**
15421     * Calculate distance for slider
15422     *
15423     * @param {EventTarget~Event} event
15424     *        The event that caused this function to run.
15425     *
15426     * @return {number}
15427     *         The current position of the Slider.
15428     *         - position.x for vertical `Slider`s
15429     *         - position.y for horizontal `Slider`s
15430     */
15431    ;
15432
15433    _proto.calculateDistance = function calculateDistance(event) {
15434      var position = getPointerPosition(this.el_, event);
15435
15436      if (this.vertical()) {
15437        return position.y;
15438      }
15439
15440      return position.x;
15441    }
15442    /**
15443     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
15444     * arrow keys. This function will only be called when the slider has focus. See
15445     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
15446     *
15447     * @param {EventTarget~Event} event
15448     *        the `keydown` event that caused this function to run.
15449     *
15450     * @listens keydown
15451     */
15452    ;
15453
15454    _proto.handleKeyDown = function handleKeyDown(event) {
15455      // Left and Down Arrows
15456      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
15457        event.preventDefault();
15458        event.stopPropagation();
15459        this.stepBack(); // Up and Right Arrows
15460      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
15461        event.preventDefault();
15462        event.stopPropagation();
15463        this.stepForward();
15464      } else {
15465        // Pass keydown handling up for unsupported keys
15466        _Component.prototype.handleKeyDown.call(this, event);
15467      }
15468    }
15469    /**
15470     * Listener for click events on slider, used to prevent clicks
15471     *   from bubbling up to parent elements like button menus.
15472     *
15473     * @param {Object} event
15474     *        Event that caused this object to run
15475     */
15476    ;
15477
15478    _proto.handleClick = function handleClick(event) {
15479      event.stopPropagation();
15480      event.preventDefault();
15481    }
15482    /**
15483     * Get/set if slider is horizontal for vertical
15484     *
15485     * @param {boolean} [bool]
15486     *        - true if slider is vertical,
15487     *        - false is horizontal
15488     *
15489     * @return {boolean}
15490     *         - true if slider is vertical, and getting
15491     *         - false if the slider is horizontal, and getting
15492     */
15493    ;
15494
15495    _proto.vertical = function vertical(bool) {
15496      if (bool === undefined) {
15497        return this.vertical_ || false;
15498      }
15499
15500      this.vertical_ = !!bool;
15501
15502      if (this.vertical_) {
15503        this.addClass('vjs-slider-vertical');
15504      } else {
15505        this.addClass('vjs-slider-horizontal');
15506      }
15507    };
15508
15509    return Slider;
15510  }(Component$1);
15511
15512  Component$1.registerComponent('Slider', Slider);
15513
15514  var percentify = function percentify(time, end) {
15515    return clamp(time / end * 100, 0, 100).toFixed(2) + '%';
15516  };
15517  /**
15518   * Shows loading progress
15519   *
15520   * @extends Component
15521   */
15522
15523
15524  var LoadProgressBar = /*#__PURE__*/function (_Component) {
15525    inheritsLoose(LoadProgressBar, _Component);
15526
15527    /**
15528     * Creates an instance of this class.
15529     *
15530     * @param {Player} player
15531     *        The `Player` that this class should be attached to.
15532     *
15533     * @param {Object} [options]
15534     *        The key/value store of player options.
15535     */
15536    function LoadProgressBar(player, options) {
15537      var _this;
15538
15539      _this = _Component.call(this, player, options) || this;
15540      _this.partEls_ = [];
15541
15542      _this.on(player, 'progress', function (e) {
15543        return _this.update(e);
15544      });
15545
15546      return _this;
15547    }
15548    /**
15549     * Create the `Component`'s DOM element
15550     *
15551     * @return {Element}
15552     *         The element that was created.
15553     */
15554
15555
15556    var _proto = LoadProgressBar.prototype;
15557
15558    _proto.createEl = function createEl$1() {
15559      var el = _Component.prototype.createEl.call(this, 'div', {
15560        className: 'vjs-load-progress'
15561      });
15562
15563      var wrapper = createEl('span', {
15564        className: 'vjs-control-text'
15565      });
15566      var loadedText = createEl('span', {
15567        textContent: this.localize('Loaded')
15568      });
15569      var separator = document_1.createTextNode(': ');
15570      this.percentageEl_ = createEl('span', {
15571        className: 'vjs-control-text-loaded-percentage',
15572        textContent: '0%'
15573      });
15574      el.appendChild(wrapper);
15575      wrapper.appendChild(loadedText);
15576      wrapper.appendChild(separator);
15577      wrapper.appendChild(this.percentageEl_);
15578      return el;
15579    };
15580
15581    _proto.dispose = function dispose() {
15582      this.partEls_ = null;
15583      this.percentageEl_ = null;
15584
15585      _Component.prototype.dispose.call(this);
15586    }
15587    /**
15588     * Update progress bar
15589     *
15590     * @param {EventTarget~Event} [event]
15591     *        The `progress` event that caused this function to run.
15592     *
15593     * @listens Player#progress
15594     */
15595    ;
15596
15597    _proto.update = function update(event) {
15598      var _this2 = this;
15599
15600      this.requestNamedAnimationFrame('LoadProgressBar#update', function () {
15601        var liveTracker = _this2.player_.liveTracker;
15602
15603        var buffered = _this2.player_.buffered();
15604
15605        var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration();
15606
15607        var bufferedEnd = _this2.player_.bufferedEnd();
15608
15609        var children = _this2.partEls_;
15610        var percent = percentify(bufferedEnd, duration);
15611
15612        if (_this2.percent_ !== percent) {
15613          // update the width of the progress bar
15614          _this2.el_.style.width = percent; // update the control-text
15615
15616          textContent(_this2.percentageEl_, percent);
15617          _this2.percent_ = percent;
15618        } // add child elements to represent the individual buffered time ranges
15619
15620
15621        for (var i = 0; i < buffered.length; i++) {
15622          var start = buffered.start(i);
15623          var end = buffered.end(i);
15624          var part = children[i];
15625
15626          if (!part) {
15627            part = _this2.el_.appendChild(createEl());
15628            children[i] = part;
15629          } //  only update if changed
15630
15631
15632          if (part.dataset.start === start && part.dataset.end === end) {
15633            continue;
15634          }
15635
15636          part.dataset.start = start;
15637          part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd)
15638
15639          part.style.left = percentify(start, bufferedEnd);
15640          part.style.width = percentify(end - start, bufferedEnd);
15641        } // remove unused buffered range elements
15642
15643
15644        for (var _i = children.length; _i > buffered.length; _i--) {
15645          _this2.el_.removeChild(children[_i - 1]);
15646        }
15647
15648        children.length = buffered.length;
15649      });
15650    };
15651
15652    return LoadProgressBar;
15653  }(Component$1);
15654
15655  Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
15656
15657  /**
15658   * Time tooltips display a time above the progress bar.
15659   *
15660   * @extends Component
15661   */
15662
15663  var TimeTooltip = /*#__PURE__*/function (_Component) {
15664    inheritsLoose(TimeTooltip, _Component);
15665
15666    /**
15667     * Creates an instance of this class.
15668     *
15669     * @param {Player} player
15670     *        The {@link Player} that this class should be attached to.
15671     *
15672     * @param {Object} [options]
15673     *        The key/value store of player options.
15674     */
15675    function TimeTooltip(player, options) {
15676      var _this;
15677
15678      _this = _Component.call(this, player, options) || this;
15679      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15680      return _this;
15681    }
15682    /**
15683     * Create the time tooltip DOM element
15684     *
15685     * @return {Element}
15686     *         The element that was created.
15687     */
15688
15689
15690    var _proto = TimeTooltip.prototype;
15691
15692    _proto.createEl = function createEl() {
15693      return _Component.prototype.createEl.call(this, 'div', {
15694        className: 'vjs-time-tooltip'
15695      }, {
15696        'aria-hidden': 'true'
15697      });
15698    }
15699    /**
15700     * Updates the position of the time tooltip relative to the `SeekBar`.
15701     *
15702     * @param {Object} seekBarRect
15703     *        The `ClientRect` for the {@link SeekBar} element.
15704     *
15705     * @param {number} seekBarPoint
15706     *        A number from 0 to 1, representing a horizontal reference point
15707     *        from the left edge of the {@link SeekBar}
15708     */
15709    ;
15710
15711    _proto.update = function update(seekBarRect, seekBarPoint, content) {
15712      var tooltipRect = findPosition(this.el_);
15713      var playerRect = getBoundingClientRect(this.player_.el());
15714      var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
15715      // for example, if the player isn't in the DOM for testing
15716
15717      if (!playerRect || !tooltipRect) {
15718        return;
15719      } // This is the space left of the `seekBarPoint` available within the bounds
15720      // of the player. We calculate any gap between the left edge of the player
15721      // and the left edge of the `SeekBar` and add the number of pixels in the
15722      // `SeekBar` before hitting the `seekBarPoint`
15723
15724
15725      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
15726      // of the player. We calculate the number of pixels from the `seekBarPoint`
15727      // to the right edge of the `SeekBar` and add to that any gap between the
15728      // right edge of the `SeekBar` and the player.
15729
15730      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
15731      // further to the right to center it over the `seekBarPoint`.
15732
15733      var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
15734      // the results of the space calculations above.
15735
15736      if (spaceLeftOfPoint < pullTooltipBy) {
15737        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15738      }
15738 else if (spaceRightOfPoint < pullTooltipBy) {
15739        pullTooltipBy = spaceRightOfPoint;
15740      } // Due to the imprecision of decimal/ratio based calculations and varying
15741      // rounding behaviors, there are cases where the spacing adjustment is off
15742      // by a pixel or two. This adds insurance to these calculations.
15743
15744
15745      if (pullTooltipBy < 0) {
15746        pullTooltipBy = 0;
15747      } else if (pullTooltipBy > tooltipRect.width) {
15748        pullTooltipBy = tooltipRect.width;
15749      } // prevent small width fluctuations within 0.4px from
15750      // changing the value below.
15751      // This really helps for live to prevent the play
15752      // progress time tooltip from jittering
15753
15754
15755      pullTooltipBy = Math.round(pullTooltipBy);
15756      this.el_.style.right = "-" + pullTooltipBy + "px";
15757      this.write(content);
15758    }
15759    /**
15760     * Write the time to the tooltip DOM element.
15761     *
15762     * @param {string} content
15763     *        The formatted time for the tooltip.
15764     */
15765    ;
15766
15767    _proto.write = function write(content) {
15768      textContent(this.el_, content);
15769    }
15770    /**
15771     * Updates the position of the time tooltip relative to the `SeekBar`.
15772     *
15773     * @param {Object} seekBarRect
15774     *        The `ClientRect` for the {@link SeekBar} element.
15775     *
15776     * @param {number} seekBarPoint
15777     *        A number from 0 to 1, representing a horizontal reference point
15778     *        from the left edge of the {@link SeekBar}
15779     *
15780     * @param {number} time
15781     *        The time to update the tooltip to, not used during live playback
15782     *
15783     * @param {Function} cb
15784     *        A function that will be called during the request animation frame
15785     *        for tooltips that need to do additional animations from the default
15786     */
15787    ;
15788
15789    _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
15790      var _this2 = this;
15791
15792      this.requestNamedAnimationFrame('TimeTooltip#updateTime', function () {
15793        var content;
15794
15795        var duration = _this2.player_.duration();
15796
15797        if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) {
15798          var liveWindow = _this2.player_.liveTracker.liveWindow();
15799
15800          var secondsBehind = liveWindow - seekBarPoint * liveWindow;
15801          content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
15802        } else {
15803          content = formatTime(time, duration);
15804        }
15805
15806        _this2.update(seekBarRect, seekBarPoint, content);
15807
15808        if (cb) {
15809          cb();
15810        }
15811      });
15812    };
15813
15814    return TimeTooltip;
15815  }(Component$1);
15816
15817  Component$1.registerComponent('TimeTooltip', TimeTooltip);
15818
15819  /**
15820   * Used by {@link SeekBar} to display media playback progress as part of the
15821   * {@link ProgressControl}.
15822   *
15823   * @extends Component
15824   */
15825
15826  var PlayProgressBar = /*#__PURE__*/function (_Component) {
15827    inheritsLoose(PlayProgressBar, _Component);
15828
15829    /**
15830     * Creates an instance of this class.
15831     *
15832     * @param {Player} player
15833     *        The {@link Player} that this class should be attached to.
15834     *
15835     * @param {Object} [options]
15836     *        The key/value store of player options.
15837     */
15838    function PlayProgressBar(player, options) {
15839      var _this;
15840
15841      _this = _Component.call(this, player, options) || this;
15842      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15843      return _this;
15844    }
15845    /**
15846     * Create the the DOM element for this class.
15847     *
15848     * @return {Element}
15849     *         The element that was created.
15850     */
15851
15852
15853    var _proto = PlayProgressBar.prototype;
15854
15855    _proto.createEl = function createEl() {
15856      return _Component.prototype.createEl.call(this, 'div', {
15857        className: 'vjs-play-progress vjs-slider-bar'
15858      }, {
15859        'aria-hidden': 'true'
15860      });
15861    }
15862    /**
15863     * Enqueues updates to its own DOM as well as the DOM of its
15864     * {@link TimeTooltip} child.
15865     *
15866     * @param {Object} seekBarRect
15867     *        The `ClientRect` for the {@link SeekBar} element.
15868     *
15869     * @param {number} seekBarPoint
15870     *        A number from 0 to 1, representing a horizontal reference point
15871     *        from the left edge of the {@link SeekBar}
15872     */
15873    ;
15874
15875    _proto.update = function update(seekBarRect, seekBarPoint) {
15876      var timeTooltip = this.getChild('timeTooltip');
15877
15878      if (!timeTooltip) {
15879        return;
15880      }
15881
15882      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15883      timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
15884    };
15885
15886    return PlayProgressBar;
15887  }(Component$1);
15888  /**
15889   * Default options for {@link PlayProgressBar}.
15890   *
15891   * @type {Object}
15892   * @private
15893   */
15894
15895
15896  PlayProgressBar.prototype.options_ = {
15897    children: []
15898  }; // Time tooltips should not be added to a player on mobile devices
15899
15900  if (!IS_IOS && !IS_ANDROID) {
15901    PlayProgressBar.prototype.options_.children.push('timeTooltip');
15902  }
15903
15904  Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
15905
15906  /**
15907   * The {@link MouseTimeDisplay} component tracks mouse movement over the
15908   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
15909   * indicating the time which is represented by a given point in the
15910   * {@link ProgressControl}.
15911   *
15912   * @extends Component
15913   */
15914
15915  var MouseTimeDisplay = /*#__PURE__*/function (_Component) {
15916    inheritsLoose(MouseTimeDisplay, _Component);
15917
15918    /**
15919     * Creates an instance of this class.
15920     *
15921     * @param {Player} player
15922     *        The {@link Player} that this class should be attached to.
15923     *
15924     * @param {Object} [options]
15925     *        The key/value store of player options.
15926     */
15927    function MouseTimeDisplay(player, options) {
15928      var _this;
15929
15930      _this = _Component.call(this, player, options) || this;
15931      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15932      return _this;
15933    }
15934    /**
15935     * Create the DOM element for this class.
15936     *
15937     * @return {Element}
15938     *         The element that was created.
15939     */
15940
15941
15942    var _proto = MouseTimeDisplay.prototype;
15943
15944    _proto.createEl = function createEl() {
15945      return _Component.prototype.createEl.call(this, 'div', {
15946        className: 'vjs-mouse-display'
15947      });
15948    }
15949    /**
15950     * Enqueues updates to its own DOM as well as the DOM of its
15951     * {@link TimeTooltip} child.
15952     *
15953     * @param {Object} seekBarRect
15954     *        The `ClientRect` for the {@link SeekBar} element.
15955     *
15956     * @param {number} seekBarPoint
15957     *        A number from 0 to 1, representing a horizontal reference point
15958     *        from the left edge of the {@link SeekBar}
15959     */
15960    ;
15961
15962    _proto.update = function update(seekBarRect, seekBarPoint) {
15963      var _this2 = this;
15964
15965      var time = seekBarPoint * this.player_.duration();
15966      this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
15967        _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
15968      });
15969    };
15970
15971    return MouseTimeDisplay;
15972  }(Component$1);
15973  /**
15974   * Default options for `MouseTimeDisplay`
15975   *
15976   * @type {Object}
15977   * @private
15978   */
15979
15980
15981  MouseTimeDisplay.prototype.options_ = {
15982    children: ['timeTooltip']
15983  };
15984  Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
15985
15986  var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
15987
15988  var PAGE_KEY_MULTIPLIER = 12;
15989  /**
15990   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
15991   * as its `bar`.
15992   *
15993   * @extends Slider
15994   */
15995
15996  var SeekBar = /*#__PURE__*/function (_Slider) {
15997    inheritsLoose(SeekBar, _Slider);
15998
15999    /**
16000     * Creates an instance of this class.
16001     *
16002     * @param {Player} player
16003     *        The `Player` that this class should be attached to.
16004     *
16005     * @param {Object} [options]
16006     *        The key/value store of player options.
16007     */
16008    function SeekBar(player, options) {
16009      var _this;
16010
16011      _this = _Slider.call(this, player, options) || this;
16012
16013      _this.setEventHandlers_();
16014
16015      return _this;
16016    }
16017    /**
16018     * Sets the event handlers
16019     *
16020     * @private
16021     */
16022
16023
16024    var _proto = SeekBar.prototype;
16025
16026    _proto.setEventHandlers_ = function setEventHandlers_() {
16027      var _this2 = this;
16028
16029      this.update_ = bind(this, this.update);
16030      this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
16031      this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
16032
16033      if (this.player_.liveTracker) {
16034        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
16035      } // when playing, let's ensure we smoothly update the play progress bar
16036      // via an interval
16037
16038
16039      this.updateInterval = null;
16040
16041      this.enableIntervalHandler_ = function (e) {
16042        return _this2.enableInterval_(e);
16043      };
16044
16045      this.disableIntervalHandler_ = function (e) {
16046        return _this2.disableInterval_(e);
16047      };
16048
16049      this.on(this.player_, ['playing'], this.enableIntervalHandler_);
16050      this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden,
16051      // also, this causes the CPU to spike and eventually crash the page on IE11.
16052
16053      if ('hidden' in document_1 && 'visibilityState' in document_1) {
16054        this.on(document_1, 'visibilitychange', this.toggleVisibility_);
16055      }
16056    };
16057
16058    _proto.toggleVisibility_ = function toggleVisibility_(e) {
16059      if (document_1.visibilityState === 'hidden') {
16060        this.cancelNamedAnimationFrame('SeekBar#update');
16061        this.cancelNamedAnimationFrame('Slider#update');
16062        this.disableInterval_(e);
16063      } else {
16064        if (!this.player_.ended() && !this.player_.paused()) {
16065          this.enableInterval_();
16066        } // we just switched back to the page and someone may be looking, so, update ASAP
16067
16068
16069        this.update();
16070      }
16071    };
16072
16073    _proto.enableInterval_ = function enableInterval_() {
16074      if (this.updateInterval) {
16075        return;
16076      }
16077
16078      this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
16079    };
16080
16081    _proto.disableInterval_ = function disableInterval_(e) {
16082      if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
16083        return;
16084      }
16085
16086      if (!this.updateInterval) {
16087        return;
16088      }
16089
16090      this.clearInterval(this.updateInterval);
16091      this.updateInterval = null;
16092    }
16093    /**
16094     * Create the `Component`'s DOM element
16095     *
16096     * @return {Element}
16097     *         The element that was created.
16098     */
16099    ;
16100
16101    _proto.createEl = function createEl() {
16102      return _Slider.prototype.createEl.call(this, 'div', {
16103        className: 'vjs-progress-holder'
16104      }, {
16105        'aria-label': this.localize('Progress Bar')
16106      });
16107    }
16108    /**
16109     * This function updates the play progress bar and accessibility
16110     * attributes to whatever is passed in.
16111     *
16112     * @param {EventTarget~Event} [event]
16113     *        The `timeupdate` or `ended` event that caused this to run.
16114     *
16115     * @listens Player#timeupdate
16116     *
16117     * @return {number}
16118     *          The current percent at a number from 0-1
16119     */
16120    ;
16121
16122    _proto.update = function update(event) {
16123      var _this3 = this;
16124
16125      // ignore updates while the tab is hidden
16126      if (document_1.visibilityState === 'hidden') {
16127        return;
16128      }
16129
16130      var percent = _Slider.prototype.update.call(this);
16131
16132      this.requestNamedAnimationFrame('SeekBar#update', function () {
16133        var currentTime = _this3.player_.ended() ? _this3.player_.duration() : _this3.getCurrentTime_();
16134        var liveTracker = _this3.player_.liveTracker;
16135
16136        var duration = _this3.player_.duration();
16137
16138        if (liveTracker && liveTracker.isLive()) {
16139          duration = _this3.player_.liveTracker.liveCurrentTime();
16140        }
16141
16142        if (_this3.percent_ !== percent) {
16143          // machine readable value of progress bar (percentage complete)
16144          _this3.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
16145
16146          _this3.percent_ = percent;
16147        }
16148
16149        if (_this3.currentTime_ !== currentTime || _this3.duration_ !== duration) {
16150          // human readable value of progress bar (time complete)
16151          _this3.el_.setAttribute('aria-valuetext', _this3.localize('progress bar timing: currentTime={1} duration={2}
16151', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
16152
16153          _this3.currentTime_ = currentTime;
16154          _this3.duration_ = duration;
16155        } // update the progress bar time tooltip with the current time
16156
16157
16158        if (_this3.bar) {
16159          _this3.bar.update(getBoundingClientRect(_this3.el()), _this3.getProgress());
16160        }
16161      });
16162      return percent;
16163    }
16164    /**
16165     * Prevent liveThreshold from causing seeks to seem like they
16166     * are not happening from a user perspective.
16167     *
16168     * @param {number} ct
16169     *        current time to seek to
16170     */
16171    ;
16172
16173    _proto.userSeek_ = function userSeek_(ct) {
16174      if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
16175        this.player_.liveTracker.nextSeekedFromUser();
16176      }
16177
16178      this.player_.currentTime(ct);
16179    }
16180    /**
16181     * Get the value of current time but allows for smooth scrubbing,
16182     * when player can't keep up.
16183     *
16184     * @return {number}
16185     *         The current time value to display
16186     *
16187     * @private
16188     */
16189    ;
16190
16191    _proto.getCurrentTime_ = function getCurrentTime_() {
16192      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
16193    }
16194    /**
16195     * Get the percentage of media played so far.
16196     *
16197     * @return {number}
16198     *         The percentage of media played so far (0 to 1).
16199     */
16200    ;
16201
16202    _proto.getPercent = function getPercent() {
16203      var currentTime = this.getCurrentTime_();
16204      var percent;
16205      var liveTracker = this.player_.liveTracker;
16206
16207      if (liveTracker && liveTracker.isLive()) {
16208        percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
16209
16210        if (liveTracker.atLiveEdge()) {
16211          percent = 1;
16212        }
16213      } else {
16214        percent = currentTime / this.player_.duration();
16215      }
16216
16217      return percent;
16218    }
16219    /**
16220     * Handle mouse down on seek bar
16221     *
16222     * @param {EventTarget~Event} event
16223     *        The `mousedown` event that caused this to run.
16224     *
16225     * @listens mousedown
16226     */
16227    ;
16228
16229    _proto.handleMouseDown = function handleMouseDown(event) {
16230      if (!isSingleLeftClick(event)) {
16231        return;
16232      } // Stop event propagation to prevent double fire in progress-control.js
16233
16234
16235      event.stopPropagation();
16236      this.player_.scrubbing(true);
16237      this.videoWasPlaying = !this.player_.paused();
16238      this.player_.pause();
16239
16240      _Slider.prototype.handleMouseDown.call(this, event);
16241    }
16242    /**
16243     * Handle mouse move on seek bar
16244     *
16245     * @param {EventTarget~Event} event
16246     *        The `mousemove` event that caused this to run.
16247     *
16248     * @listens mousemove
16249     */
16250    ;
16251
16252    _proto.handleMouseMove = function handleMouseMove(event) {
16253      if (!isSingleLeftClick(event)) {
16254        return;
16255      }
16256
16257      var newTime;
16258      var distance = this.calculateDistance(event);
16259      var liveTracker = this.player_.liveTracker;
16260
16261      if (!liveTracker || !liveTracker.isLive()) {
16262        newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
16263
16264        if (newTime === this.player_.duration()) {
16265          newTime = newTime - 0.1;
16266        }
16267      } else {
16268        if (distance >= 0.99) {
16269          liveTracker.seekToLiveEdge();
16270          return;
16271        }
16272
16273        var seekableStart = liveTracker.seekableStart();
16274        var seekableEnd = liveTracker.liveCurrentTime();
16275        newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
16276
16277        if (newTime >= seekableEnd) {
16278          newTime = seekableEnd;
16279        } // Compensate for precision differences so that currentTime is not less
16280        // than seekable start
16281
16282
16283        if (newTime <= seekableStart) {
16284          newTime = seekableStart + 0.1;
16285        } // On android seekableEnd can be Infinity sometimes,
16286        // this will cause newTime to be Infinity, which is
16287        // not a valid currentTime.
16288
16289
16290        if (newTime === Infinity) {
16291          return;
16292        }
16293      } // Set new time (tell player to seek to new time)
16294
16295
16296      this.userSeek_(newTime);
16297    };
16298
16299    _proto.enable = function enable() {
16300      _Slider.prototype.enable.call(this);
16301
16302      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
16303
16304      if (!mouseTimeDisplay) {
16305        return;
16306      }
16307
16308      mouseTimeDisplay.show();
16309    };
16310
16311    _proto.disable = function disable() {
16312      _Slider.prototype.disable.call(this);
16313
16314      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
16315
16316      if (!mouseTimeDisplay) {
16317        return;
16318      }
16319
16320      mouseTimeDisplay.hide();
16321    }
16322    /**
16323     * Handle mouse up on seek bar
16324     *
16325     * @param {EventTarget~Event} event
16326     *        The `mouseup` event that caused this to run.
16327     *
16328     * @listens mouseup
16329     */
16330    ;
16331
16332    _proto.handleMouseUp = function handleMouseUp(event) {
16333      _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
16334
16335
16336      if (event) {
16337        event.stopPropagation();
16338      }
16339
16340      this.player_.scrubbing(false);
16341      /**
16342       * Trigger timeupdate because we're done seeking and the time has changed.
16343       * This is particularly useful for if the player is paused to time the time displays.
16344       *
16345       * @event Tech#timeupdate
16346       * @type {EventTarget~Event}
16347       */
16348
16349      this.player_.trigger({
16350        type: 'timeupdate',
16351        target: this,
16352        manuallyTriggered: true
16353      });
16354
16355      if (this.videoWasPlaying) {
16356        silencePromise(this.player_.play());
16357      } else {
16358        // We're done seeking and the time has changed.
16359        // If the player is paused, make sure we display the correct time on the seek bar.
16360        this.update_();
16361      }
16362    }
16363    /**
16364     * Move more quickly fast forward for keyboard-only users
16365     */
16366    ;
16367
16368    _proto.stepForward = function stepForward() {
16369      this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
16370    }
16371    /**
16372     * Move more quickly rewind for keyboard-only users
16373     */
16374    ;
16375
16376    _proto.stepBack = function stepBack() {
16377      this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
16378    }
16379    /**
16380     * Toggles the playback state of the player
16381     * This gets called when enter or space is used on the seekbar
16382     *
16383     * @param {EventTarget~Event} event
16384     *        The `keydown` event that caused this function to be called
16385     *
16386     */
16387    ;
16388
16389    _proto.handleAction = function handleAction(event) {
16390      if (this.player_.paused()) {
16391        this.player_.play();
16392      } else {
16393        this.player_.pause();
16394      }
16395    }
16396    /**
16397     * Called when this SeekBar has focus and a key gets pressed down.
16398     * Supports the following keys:
16399     *
16400     *   Space or Enter key fire a click event
16401     *   Home key moves to start of the timeline
16402     *   End key moves to end of the timeline
16403     *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
16404     *   PageDown key moves back a larger step than ArrowDown
16405     *   PageUp key moves forward a large step
16406     *
16407     * @param {EventTarget~Event} event
16408     *        The `keydown` event that caused this function to be called.
16409     *
16410     * @listens keydown
16411     */
16412    ;
16413
16414    _proto.handleKeyDown = function handleKeyDown(event) {
16415      var liveTracker = this.player_.liveTracker;
16416
16417      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
16418        event.preventDefault();
16419        event.stopPropagation();
16420        this.handleAction(event);
16421      } else if (keycode.isEventKey(event, 'Home')) {
16422        event.preventDefault();
16423        event.stopPropagation();
16424        this.userSeek_(0);
16425      } else if (keycode.isEventKey(event, 'End')) {
16426        event.preventDefault();
16427        event.stopPropagation();
16428
16429        if (liveTracker && liveTracker.isLive()) {
16430          this.userSeek_(liveTracker.liveCurrentTime());
16431        } else {
16432          this.userSeek_(this.player_.duration());
16433        }
16434      } else if (/^[0-9]$/.test(keycode(event))) {
16435        event.preventDefault();
16436        event.stopPropagation();
16437        var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
16438
16439        if (liveTracker && liveTracker.isLive()) {
16440          this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
16441        } else {
16442          this.userSeek_(this.player_.duration() * gotoFraction);
16443        }
16444      } else if (keycode.isEventKey(event, 'PgDn')) {
16445        event.preventDefault();
16446        event.stopPropagation();
16447        this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
16448      } else if (keycode.isEventKey(event, 'PgUp')) {
16449        event.preventDefault();
16450        event.stopPropagation();
16451        this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
16452      } else {
16453        // Pass keydown handling up for unsupported keys
16454        _Slider.prototype.handleKeyDown.call(this, event);
16455      }
16456    };
16457
16458    _proto.dispose = function dispose() {
16459      this.disableInterval_();
16460      this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
16461
16462      if (this.player_.liveTracker) {
16463        this.off(this.player_.liveTracker, 'liveedgechange', this.update);
16464      }
16465
16466      this.off(this.player_, ['playing'], this.enableIntervalHandler_);
16467      this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden,
16468      // also, this causes the CPU to spike and eventually crash the page on IE11.
16469
16470      if ('hidden' in document_1 && 'visibilityState' in document_1) {
16471        this.off(document_1, 'visibilitychange', this.toggleVisibility_);
16472      }
16473
16474      _Slider.prototype.dispose.call(this);
16475    };
16476
16477    return SeekBar;
16478  }
16478(Slider);
16479  /**
16480   * Default options for the `SeekBar`
16481   *
16482   * @type {Object}
16483   * @private
16484   */
16485
16486
16487  SeekBar.prototype.options_ = {
16488    children: ['loadProgressBar', 'playProgressBar'],
16489    barName: 'playProgressBar'
16490  }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
16491
16492  if (!IS_IOS && !IS_ANDROID) {
16493    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
16494  }
16495
16496  Component$1.registerComponent('SeekBar', SeekBar);
16497
16498  /**
16499   * The Progress Control component contains the seek bar, load progress,
16500   * and play progress.
16501   *
16502   * @extends Component
16503   */
16504
16505  var ProgressControl = /*#__PURE__*/function (_Component) {
16506    inheritsLoose(ProgressControl, _Component);
16507
16508    /**
16509     * Creates an instance of this class.
16510     *
16511     * @param {Player} player
16512     *        The `Player` that this class should be attached to.
16513     *
16514     * @param {Object} [options]
16515     *        The key/value store of player options.
16516     */
16517    function ProgressControl(player, options) {
16518      var _this;
16519
16520      _this = _Component.call(this, player, options) || this;
16521      _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
16522      _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
16523
16524      _this.handleMouseUpHandler_ = function (e) {
16525        return _this.handleMouseUp(e);
16526      };
16527
16528      _this.handleMouseDownHandler_ = function (e) {
16529        return _this.handleMouseDown(e);
16530      };
16531
16532      _this.enable();
16533
16534      return _this;
16535    }
16536    /**
16537     * Create the `Component`'s DOM element
16538     *
16539     * @return {Element}
16540     *         The element that was created.
16541     */
16542
16543
16544    var _proto = ProgressControl.prototype;
16545
16546    _proto.createEl = function createEl() {
16547      return _Component.prototype.createEl.call(this, 'div', {
16548        className: 'vjs-progress-control vjs-control'
16549      });
16550    }
16551    /**
16552     * When the mouse moves over the `ProgressControl`, the pointer position
16553     * gets passed down to the `MouseTimeDisplay` component.
16554     *
16555     * @param {EventTarget~Event} event
16556     *        The `mousemove` event that caused this function to run.
16557     *
16558     * @listen mousemove
16559     */
16560    ;
16561
16562    _proto.handleMouseMove = function handleMouseMove(event) {
16563      var seekBar = this.getChild('seekBar');
16564
16565      if (!seekBar) {
16566        return;
16567      }
16568
16569      var playProgressBar = seekBar.getChild('playProgressBar');
16570      var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
16571
16572      if (!playProgressBar && !mouseTimeDisplay) {
16573        return;
16574      }
16575
16576      var seekBarEl = seekBar.el();
16577      var seekBarRect = findPosition(seekBarEl);
16578      var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
16579      // that it's possible to trigger this behavior outside the boundaries of
16580      // the `SeekBar`. This ensures we stay within it at all times.
16581
16582      seekBarPoint = clamp(seekBarPoint, 0, 1);
16583
16584      if (mouseTimeDisplay) {
16585        mouseTimeDisplay.update(seekBarRect, seekBarPoint);
16586      }
16587
16588      if (playProgressBar) {
16589        playProgressBar.update(seekBarRect, seekBar.getProgress());
16590      }
16591    }
16592    /**
16593     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
16594     *
16595     * @method ProgressControl#throttledHandleMouseSeek
16596     * @param {EventTarget~Event} event
16597     *        The `mousemove` event that caused this function to run.
16598     *
16599     * @listen mousemove
16600     * @listen touchmove
16601     */
16602
16603    /**
16604     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
16605     *
16606     * @param {EventTarget~Event} event
16607     *        `mousedown` or `touchstart` event that triggered this function
16608     *
16609     * @listens mousemove
16610     * @listens touchmove
16611     */
16612    ;
16613
16614    _proto.handleMouseSeek = function handleMouseSeek(event) {
16615      var seekBar = this.getChild('seekBar');
16616
16617      if (seekBar) {
16618        seekBar.handleMouseMove(event);
16619      }
16620    }
16621    /**
16622     * Are controls are currently enabled for this progress control.
16623     *
16624     * @return {boolean}
16625     *         true if controls are enabled, false otherwise
16626     */
16627    ;
16628
16629    _proto.enabled = function enabled() {
16630      return this.enabled_;
16631    }
16632    /**
16633     * Disable all controls on the progress control and its children
16634     */
16635    ;
16636
16637    _proto.disable = function disable() {
16638      this.children().forEach(function (child) {
16639        return child.disable && child.disable();
16640      });
16641
16642      if (!this.enabled()) {
16643        return;
16644      }
16645
16646      this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
16647      this.off(this.el_, 'mousemove', this.handleMouseMove);
16648      this.removeListenersAddedOnMousedownAndTouchstart();
16649      this.addClass('disabled');
16650      this.enabled_ = false; // Restore normal playback state if controls are disabled while scrubbing
16651
16652      if (this.player_.scrubbing()) {
16653        var seekBar = this.getChild('seekBar');
16654        this.player_.scrubbing(false);
16655
16656        if (seekBar.videoWasPlaying) {
16657          silencePromise(this.player_.play());
16658        }
16659      }
16660    }
16661    /**
16662     * Enable all controls on the progress control and its children
16663     */
16664    ;
16665
16666    _proto.enable = function enable() {
16667      this.children().forEach(function (child) {
16668        return child.enable && child.enable();
16669      });
16670
16671      if (this.enabled()) {
16672        return;
16673      }
16674
16675      this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
16676      this.on(this.el_, 'mousemove', this.handleMouseMove);
16677      this.removeClass('disabled');
16678      this.enabled_ = true;
16679    }
16680    /**
16681     * Cleanup listeners after the user finishes interacting with the progress controls
16682     */
16683    ;
16684
16685    _proto.removeListenersAddedOnMousedownAndTouchstart = function removeListenersAddedOnMousedownAndTouchstart() {
16686      var doc = this.el_.ownerDocument;
16687      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
16688      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
16689      this.off(doc, 'mouseup', this.handleMouseUpHandler_);
16690      this.off(doc, 'touchend', this.handleMouseUpHandler_);
16691    }
16692    /**
16693     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
16694     *
16695     * @param {EventTarget~Event} event
16696     *        `mousedown` or `touchstart` event that triggered this function
16697     *
16698     * @listens mousedown
16699     * @listens touchstart
16700     */
16701    ;
16702
16703    _proto.handleMouseDown = function handleMouseDown(event) {
16704      var doc = this.el_.ownerDocument;
16705      var seekBar = this.getChild('seekBar');
16706
16707      if (seekBar) {
16708        seekBar.handleMouseDown(event);
16709      }
16710
16711      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
16712      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
16713      this.on(doc, 'mouseup', this.handleMouseUpHandler_);
16714      this.on(doc, 'touchend', this.handleMouseUpHandler_);
16715    }
16716    /**
16717     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
16718     *
16719     * @param {EventTarget~Event} event
16720     *        `mouseup` or `touchend` event that triggered this function.
16721     *
16722     * @listens touchend
16723     * @listens mouseup
16724     */
16725    ;
16726
16727    _proto.handleMouseUp = function handleMouseUp(event) {
16728      var seekBar = this.getChild('seekBar');
16729
16730      if (seekBar) {
16731        seekBar.handleMouseUp(event);
16732      }
16733
16734      this.removeListenersAddedOnMousedownAndTouchstart();
16735    };
16736
16737    return ProgressControl;
16738  }(Component$1);
16739  /**
16740   * Default options for `ProgressControl`
16741   *
16742   * @type {Object}
16743   * @private
16744   */
16745
16746
16747  ProgressControl.prototype.options_ = {
16748    children: ['seekBar']
16749  };
16750  Component$1.registerComponent('ProgressControl', ProgressControl);
16751
16752  /**
16753   * Toggle Picture-in-Picture mode
16754   *
16755   * @extends Button
16756   */
16757
16758  var PictureInPictureToggle = /*#__PURE__*/function (_Button) {
16759    inheritsLoose(PictureInPictureToggle, _Button);
16760
16761    /**
16762     * Creates an instance of this class.
16763     *
16764     * @param {Player} player
16765     *        The `Player` that this class should be attached to.
16766     *
16767     * @param {Object} [options]
16768     *        The key/value store of player options.
16769     *
16770     * @listens Player#enterpictureinpicture
16771     * @listens Player#leavepictureinpicture
16772     */
16773    function PictureInPictureToggle(player, options) {
16774      var _this;
16775
16776      _this = _Button.call(this, player, options) || this;
16777
16778      _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], function (e) {
16779        return _this.handlePictureInPictureChange(e);
16780      });
16781
16782      _this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], function (e) {
16783        return _this.handlePictureInPictureEnabledChange(e);
16784      }); // TODO: Deactivate button on player emptied event.
16785
16786
16787      _this.disable();
16788
16789      return _this;
16790    }
16791    /**
16792     * Builds the default DOM `className`.
16793     *
16794     * @return {string}
16795     *         The DOM `className` for this object.
16796     */
16797
16798
16799    var _proto = PictureInPictureToggle.prototype;
16800
16801    _proto.buildCSSClass = function buildCSSClass() {
16802      return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this);
16803    }
16804    /**
16805     * Enables or disables button based on document.pictureInPictureEnabled property value
16806     * or on value returned by player.disablePictureInPicture() method.
16807     */
16808    ;
16809
16810    _proto.handlePictureInPictureEnabledChange = function handlePictureInPictureEnabledChange() {
16811      if (document_1.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) {
16812        this.enable();
16813      } else {
16814        this.disable();
16815      }
16816    }
16817    /**
16818     * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
16819     *
16820     * @param {EventTarget~Event} [event]
16821     *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
16822     *        called.
16823     *
16824     * @listens Player#enterpictureinpicture
16825     * @listens Player#leavepictureinpicture
16826     */
16827    ;
16828
16829    _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) {
16830      if (this.player_.isInPictureInPicture()) {
16831        this.controlText('Exit Picture-in-Picture');
16832      } else {
16833        this.controlText('Picture-in-Picture');
16834      }
16835
16836      this.handlePictureInPictureEnabledChange();
16837    }
16838    /**
16839     * This gets called when an `PictureInPictureToggle` is "clicked". See
16840     * {@link ClickableComponent} for more detailed information on what a click can be.
16841     *
16842     * @param {EventTarget~Event} [event]
16843     *        The `keydown`, `tap`, or `click` event that caused this function to be
16844     *        called.
16845     *
16846     * @listens tap
16847     * @listens click
16848     */
16849    ;
16850
16851    _proto.handleClick = function handleClick(event) {
16852      if (!this.player_.isInPictureInPicture()) {
16853        this.player_.requestPictureInPicture();
16854      } else {
16855        this.player_.exitPictureInPicture();
16856      }
16857    };
16858
16859    return PictureInPictureToggle;
16860  }(Button);
16861  /**
16862   * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
16863   *
16864   * @type {string}
16865   * @private
16866   */
16867
16868
16869  PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
16870  Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
16871
16872  /**
16873   * Toggle fullscreen video
16874   *
16875   * @extends Button
16876   */
16877
16878  var FullscreenToggle = /*#__PURE__*/function (_Button) {
16879    inheritsLoose(FullscreenToggle, _Button);
16880
16881    /**
16882     * Creates an instance of this class.
16883     *
16884     * @param {Player} player
16885     *        The `Player` that this class should be attached to.
16886     *
16887     * @param {Object} [options]
16888     *        The key/value store of player options.
16889     */
16890    function FullscreenToggle(player, options) {
16891      var _this;
16892
16893      _this = _Button.call(this, player, options) || this;
16894
16895      _this.on(player, 'fullscreenchange', function (e) {
16896        return _this.handleFullscreenChange(e);
16897      });
16898
16899      if (document_1[player.fsApi_.fullscreenEnabled] === false) {
16900        _this.disable();
16901      }
16902
16903      return _this;
16904    }
16905    /**
16906     * Builds the default DOM `className`.
16907     *
16908     * @return {string}
16909     *         The DOM `className` for this object.
16910     */
16911
16912
16913    var _proto = FullscreenToggle.prototype;
16914
16915    _proto.buildCSSClass = function buildCSSClass() {
16916      return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
16917    }
16918    /**
16919     * Handles fullscreenchange on the player and change control text accordingly.
16920     *
16921     * @param {EventTarget~Event} [event]
16922     *        The {@link Player#fullscreenchange} event that caused this function to be
16923     *        called.
16924     *
16925     * @listens Player#fullscreenchange
16926     */
16927    ;
16928
16929    _proto.handleFullscreenChange = function handleFullscreenChange(event) {
16930      if (this.player_.isFullscreen()) {
16931        this.controlText('Non-Fullscreen');
16932      } else {
16933        this.controlText('Fullscreen');
16934      }
16935    }
16936    /**
16937     * This gets called when an `FullscreenToggle` is "clicked". See
16938     * {@link ClickableComponent} for more detailed information on what a click can be.
16939     *
16940     * @param {EventTarget~Event} [event]
16941     *        The `keydown`, `tap`, or `click` event that caused this function to be
16942     *        called.
16943     *
16944     * @listens tap
16945     * @listens click
16946     */
16947    ;
16948
16949    _proto.handleClick = function handleClick(event) {
16950      if (!this.player_.isFullscreen()) {
16951        this.player_.requestFullscreen();
16952      } else {
16953        this.player_.exitFullscreen();
16954      }
16955    };
16956
16957    return FullscreenToggle;
16958  }(Button);
16959  /**
16960   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
16961   *
16962   * @type {string}
16963   * @private
16964   */
16965
16966
16967  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
16968  Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
16969
16970  /**
16971   * Check if volume control is supported and if it isn't hide the
16972   * `Component` that was passed  using the `vjs-hidden` class.
16973   *
16974   * @param {Component} self
16975   *        The component that should be hidden if volume is unsupported
16976   *
16977   * @param {Player} player
16978   *        A reference to the player
16979   *
16980   * @private
16981   */
16982  var checkVolumeSupport = function checkVolumeSupport(self, player) {
16983    // hide volume controls when they're not supported by the current tech
16984    if (player.tech_ && !player.tech_.featuresVolumeControl) {
16985      self.addClass('vjs-hidden');
16986    }
16987
16988    self.on(player, 'loadstart', function () {
16989      if (!player.tech_.featuresVolumeControl) {
16990        self.addClass('vjs-hidden');
16991      } else {
16992        self.removeClass('vjs-hidden');
16993      }
16994    });
16995  };
16996
16997  /**
16998   * Shows volume level
16999   *
17000   * @extends Component
17001   */
17002
17003  var VolumeLevel = /*#__PURE__*/function (_Component) {
17004    inheritsLoose(VolumeLevel, _Component);
17005
17006    function VolumeLevel() {
17007      return _Component.apply(this, arguments) || this;
17008    }
17009
17010    var _proto = VolumeLevel.prototype;
17011
17012    /**
17013     * Create the `Component`'s DOM element
17014     *
17015     * @return {Element}
17016     *         The element that was created.
17017     */
17018    _proto.createEl = function createEl() {
17019      var el = _Component.prototype.createEl.call(this, 'div', {
17020        className: 'vjs-volume-level'
17021      });
17022
17023      el.appendChild(_Component.prototype.createEl.call(this, 'span', {
17024        className: 'vjs-control-text'
17025      }));
17026      return el;
17027    };
17028
17029    return VolumeLevel;
17030  }(Component$1);
17031
17032  Component$1.registerComponent('VolumeLevel', VolumeLevel);
17033
17034  /**
17035   * Volume level tooltips display a volume above or side by side the volume bar.
17036   *
17037   * @extends Component
17038   */
17039
17040  var VolumeLevelTooltip = /*#__PURE__*/function (_Component) {
17041    inheritsLoose(VolumeLevelTooltip, _Component);
17042
17043    /**
17044     * Creates an instance of this class.
17045     *
17046     * @param {Player} player
17047     *        The {@link Player} that this class should be attached to.
17048     *
17049     * @param {Object} [options]
17050     *        The key/value store of player options.
17051     */
17052    function VolumeLevelTooltip(player, options) {
17053      var _this;
17054
17055      _this = _Component.call(this, player, options) || this;
17056      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
17057      return _this;
17058    }
17059    /**
17060     * Create the volume tooltip DOM element
17061     *
17062     * @return {Element}
17063     *         The element that was created.
17064     */
17065
17066
17067    var _proto = VolumeLevelTooltip.prototype;
17068
17069    _proto.createEl = function createEl() {
17070      return _Component.prototype.createEl.call(this, 'div', {
17071        className: 'vjs-volume-tooltip'
17072      }, {
17073        'aria-hidden': 'true'
17074      });
17075    }
17076    /**
17077     * Updates the position of the tooltip relative to the `VolumeBar` and
17078     * its content text.
17079     *
17080     * @param {Object} rangeBarRect
17081     *        The `ClientRect` for the {@link VolumeBar} element.
17082     *
17083     * @param {number} rangeBarPoint
17084     *        A number from 0 to 1, representing a horizontal/vertical reference point
17085     *        from the left edge of the {@link VolumeBar}
17086     *
17087     * @param {boolean} vertical
17088     *        Referees to the Volume control position
17089     *        in the control bar{@link VolumeControl}
17090     *
17091     */
17092    ;
17093
17094    _proto.update = function update(rangeBarRect, rangeBarPoint, vertical, content) {
17095      if (!vertical) {
17096        var tooltipRect = getBoundingClientRect(this.el_);
17097        var playerRect = getBoundingClientRect(this.player_.el());
17098        var volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
17099
17100        if (!playerRect || !tooltipRect) {
17101          return;
17102        }
17103
17104        var spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
17105        var spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
17106        var pullTooltipBy = tooltipRect.width / 2;
17107
17108        if (spaceLeftOfPoint < pullTooltipBy) {
17109          pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
17110        } else if (spaceRightOfPoint < pullTooltipBy) {
17111          pullTooltipBy = spaceRightOfPoint;
17112        }
17113
17114        if (pullTooltipBy < 0) {
17115          pullTooltipBy = 0;
17116        } else if (pullTooltipBy > tooltipRect.width) {
17117          pullTooltipBy = tooltipRect.width;
17118        }
17119
17120        this.el_.style.right = "-" + pullTooltipBy + "px";
17121      }
17122
17123      this.write(content + "%");
17124    }
17125    /**
17126     * Write the volume to the tooltip DOM element.
17127     *
17128     * @param {string} content
17129     *        The formatted volume for the tooltip.
17130     */
17131    ;
17132
17133    _proto.write = function write(content) {
17134      textContent(this.el_, content);
17135    }
17136    /**
17137     * Updates the position of the volume tooltip relative to the `VolumeBar`.
17138     *
17139     * @param {Object} rangeBarRect
17140     *        The `ClientRect` for the {@link VolumeBar} element.
17141     *
17142     * @param {number} rangeBarPoint
17143     *        A number from 0 to 1, representing a horizontal/vertical reference point
17144     *        from the left edge of the {@link VolumeBar}
17145     *
17146     * @param {boolean} vertical
17147     *        Referees to the Volume control position
17148     *        in the control bar{@link VolumeControl}
17149     *
17150     * @param {number} volume
17151     *        The volume level to update the tooltip to
17152     *
17153     * @param {Function} cb
17154     *        A function that will be called during the request animation frame
17155     *        for tooltips that need to do additional animations from the default
17156     */
17157    ;
17158
17159    _proto.updateVolume = function updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
17160      var _this2 = this;
17161
17162      this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', function () {
17163        _this2.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
17164
17165        if (cb) {
17166          cb();
17167        }
17168      });
17169    };
17170
17171    return VolumeLevelTooltip;
17172  }(Component$1);
17173
17174  Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
17175
17176  /**
17177   * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
17178   * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
17179   * indicating the volume level which is represented by a given point in the
17180   * {@link VolumeBar}.
17181   *
17182   * @extends Component
17183   */
17184
17185  var MouseVolumeLevelDisplay = /*#__PURE__*/function (_Component) {
17186    inheritsLoose(MouseVolumeLevelDisplay, _Component);
17187
17188    /**
17189     * Creates an instance of this class.
17190     *
17191     * @param {Player} player
17192     *        The {@link Player} that this class should be attached to.
17193     *
17194     * @param {Object} [options]
17195     *        The key/value store of player options.
17196     */
17197    function MouseVolumeLevelDisplay(player, options) {
17198      var _this;
17199
17200      _this = _Component.call(this, player, options) || this;
17201      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
17202      return _this;
17203    }
17204    /**
17205     * Create the DOM element for this class.
17206     *
17207     * @return {Element}
17208     *         The element that was created.
17209     */
17210
17211
17212    var _proto = MouseVolumeLevelDisplay.prototype;
17213
17214    _proto.createEl = function createEl() {
17215      return _Component.prototype.createEl.call(this, 'div', {
17216        className: 'vjs-mouse-display'
17217      });
17218    }
17219    /**
17220     * Enquires updates to its own DOM as well as the DOM of its
17221     * {@link VolumeLevelTooltip} child.
17222     *
17223     * @param {Object} rangeBarRect
17224     *        The `ClientRect` for the {@link VolumeBar} element.
17225     *
17226     * @param {number} rangeBarPoint
17227     *        A number from 0 to 1, representing a horizontal/vertical reference point
17228     *        from the left edge of the {@link VolumeBar}
17229     *
17230     * @param {boolean} vertical
17231     *        Referees to the Volume control position
17232     *        in the control bar{@link VolumeControl}
17233     *
17234     */
17235    ;
17236
17237    _proto.update = function update(rangeBarRect, rangeBarPoint, vertical) {
17238      var _this2 = this;
17239
17240      var volume = 100 * rangeBarPoint;
17241      this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, function () {
17242        if (vertical) {
17243          _this2.el_.style.bottom = rangeBarRect.height * rangeBarPoint + "px";
17244        } else {
17245          _this2.el_.style.left = rangeBarRect.width * rangeBarPoint + "px";
17246        }
17247      });
17248    };
17249
17250    return MouseVolumeLevelDisplay;
17251  }(Component$1);
17252  /**
17253   * Default options for `MouseVolumeLevelDisplay`
17254   *
17255   * @type {Object}
17256   * @private
17257   */
17258
17259
17260  MouseVolumeLevelDisplay.prototype.options_ = {
17261    children: ['volumeLevelTooltip']
17262  };
17263  Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
17264
17265  /**
17266   * The bar that contains the volume level and can be clicked on to adjust the level
17267   *
17268   * @extends Slider
17269   */
17270
17271  var VolumeBar = /*#__PURE__*/function (_Slider) {
17272    inheritsLoose(VolumeBar, _Slider);
17273
17274    /**
17275     * Creates an instance of this class.
17276     *
17277     * @param {Player} player
17278     *        The `Player` that this class should be attached to.
17279     *
17280     * @param {Object} [options]
17281     *        The key/value store of player options.
17282     */
17283    function VolumeBar(player, options) {
17284      var _this;
17285
17286      _this = _Slider.call(this, player, options) || this;
17287
17288      _this.on('slideractive', function (e) {
17289        return _this.updateLastVolume_(e);
17290      });
17291
17292      _this.on(player, 'volumechange', function (e) {
17293        return _this.updateARIAAttributes(e);
17294      });
17295
17296      player.ready(function () {
17297        return _this.updateARIAAttributes();
17298      });
17299      return _this;
17300    }
17301    /**
17302     * Create the `Component`'s DOM element
17303     *
17304     * @return {Element}
17305     *         The element that was created.
17306     */
17307
17308
17309    var _proto = VolumeBar.prototype;
17310
17311    _proto.createEl = function createEl() {
17312      return _Slider.prototype.createEl.call(this, 'div', {
17313        className: 'vjs-volume-bar vjs-slider-bar'
17314      }, {
17315        'aria-label': this.localize('Volume Level'),
17316        'aria-live': 'polite'
17317      });
17318    }
17319    /**
17320     * Handle mouse down on volume bar
17321     *
17322     * @param {EventTarget~Event} event
17323     *        The `mousedown` event that caused this to run.
17324     *
17325     * @listens mousedown
17326     */
17327    ;
17328
17329    _proto.handleMouseDown = function handleMouseDown(event) {
17330      if (!isSingleLeftClick(event)) {
17331        return;
17332      }
17333
17334      _Slider.prototype.handleMouseDown.call(this, event);
17335    }
17336    /**
17337     * Handle movement events on the {@link VolumeMenuButton}.
17338     *
17339     * @param {EventTarget~Event} event
17340     *        The event that caused this function to run.
17341     *
17342     * @listens mousemove
17343     */
17344    ;
17345
17346    _proto.handleMouseMove = function handleMouseMove(event) {
17347      var mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
17348
17349      if (mouseVolumeLevelDisplay) {
17350        var volumeBarEl = this.el();
17351        var volumeBarRect = getBoundingClientRect(volumeBarEl);
17352        var vertical = this.vertical();
17353        var volumeBarPoint = getPointerPosition(volumeBarEl, event);
17354        volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x; // The default skin has a gap on either side of the `VolumeBar`. This means
17355        // that it's possible to trigger this behavior outside the boundaries of
17356        // the `VolumeBar`. This ensures we stay within it at all times.
17357
17358        volumeBarPoint = clamp(volumeBarPoint, 0, 1);
17359        mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
17360      }
17361
17362      if (!isSingleLeftClick(event)) {
17363        return;
17364      }
17365
17366      this.checkMuted();
17367      this.player_.volume(this.calculateDistance(event));
17368    }
17369    /**
17370     * If the player is muted unmute it.
17371     */
17372    ;
17373
17374    _proto.checkMuted = function checkMuted() {
17375      if (this.player_.muted()) {
17376        this.player_.muted(false);
17377      }
17378    }
17379    /**
17380     * Get percent of volume level
17381     *
17382     * @return {number}
17383     *         Volume level percent as a decimal number.
17384     */
17385    ;
17386
17387    _proto.getPercent = function getPercent() {
17388      if (this.player_.muted()) {
17389        return 0;
17390      }
17391
17392      return this.player_.volume();
17393    }
17394    /**
17395     * Increase volume level for keyboard users
17396     */
17397    ;
17398
17399    _proto.stepForward = function stepForward() {
17400      this.checkMuted();
17401      this.player_.volume(this.player_.volume() + 0.1);
17402    }
17403    /**
17404     * Decrease volume level for keyboard users
17405     */
17406    ;
17407
17408    _proto.stepBack = function stepBack() {
17409      this.checkMuted();
17410      this.player_.volume(this.player_.volume() - 0.1);
17411    }
17412    /**
17413     * Update ARIA accessibility attributes
17414     *
17415     * @param {EventTarget~Event} [event]
17416     *        The `volumechange` event that caused this function to run.
17417     *
17418     * @listens Player#volumechange
17419     */
17420    ;
17421
17422    _proto.updateARIAAttributes = function updateARIAAttributes(event) {
17423      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
17424      this.el_.setAttribute('aria-valuenow', ariaValue);
17425      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
17426    }
17427    /**
17428     * Returns the current value of the player volume as a percentage
17429     *
17430     * @private
17431     */
17432    ;
17433
17434    _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
17435      return Math.round(this.player_.volume() * 100);
17436    }
17437    /**
17438     * When user starts dragging the VolumeBar, store the volume and listen for
17439     * the end of the drag. When the drag ends, if the volume was set to zero,
17440     * set lastVolume to the stored volume.
17441     *
17442     * @listens slideractive
17443     * @private
17444     */
17445    ;
17446
17447    _proto.updateLastVolume_ = function updateLastVolume_() {
17448      var _this2 = this;
17449
17450      var volumeBeforeDrag = this.player_.volume();
17451      this.one('sliderinactive', function () {
17452        if (_this2.player_.volume() === 0) {
17453          _this2.player_.lastVolume_(volumeBeforeDrag);
17454        }
17455      });
17456    };
17457
17458    return VolumeBar;
17459  }
17459(Slider);
17460  /**
17461   * Default options for the `VolumeBar`
17462   *
17463   * @type {Object}
17464   * @private
17465   */
17466
17467
17468  VolumeBar.prototype.options_ = {
17469    children: ['volumeLevel'],
17470    barName: 'volumeLevel'
17471  }; // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
17472
17473  if (!IS_IOS && !IS_ANDROID) {
17474    VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
17475  }
17476  /**
17477   * Call the update event for this Slider when this event happens on the player.
17478   *
17479   * @type {string}
17480   */
17481
17482
17483  VolumeBar.prototype.playerEvent = 'volumechange';
17484  Component$1.registerComponent('VolumeBar', VolumeBar);
17485
17486  /**
17487   * The component for controlling the volume level
17488   *
17489   * @extends Component
17490   */
17491
17492  var VolumeControl = /*#__PURE__*/function (_Component) {
17493    inheritsLoose(VolumeControl, _Component);
17494
17495    /**
17496     * Creates an instance of this class.
17497     *
17498     * @param {Player} player
17499     *        The `Player` that this class should be attached to.
17500     *
17501     * @param {Object} [options={}]
17502     *        The key/value store of player options.
17503     */
17504    function VolumeControl(player, options) {
17505      var _this;
17506
17507      if (options === void 0) {
17508        options = {};
17509      }
17510
17511      options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
17512      // the VolumeBar is turned on.
17513
17514      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
17515        options.volumeBar = options.volumeBar || {};
17516        options.volumeBar.vertical = options.vertical;
17517      }
17518
17519      _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
17520
17521      checkVolumeSupport(assertThisInitialized(_this), player);
17522      _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
17523
17524      _this.handleMouseUpHandler_ = function (e) {
17525        return _this.handleMouseUp(e);
17526      };
17527
17528      _this.on('mousedown', function (e) {
17529        return _this.handleMouseDown(e);
17530      });
17531
17532      _this.on('touchstart', function (e) {
17533        return _this.handleMouseDown(e);
17534      });
17535
17536      _this.on('mousemove', function (e) {
17537        return _this.handleMouseMove(e);
17538      }); // while the slider is active (the mouse has been pressed down and
17539      // is dragging) or in focus we do not want to hide the VolumeBar
17540
17541
17542      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
17543        _this.volumeBar.addClass('vjs-slider-active');
17544
17545        _this.addClass('vjs-slider-active');
17546
17547        _this.trigger('slideractive');
17548      });
17549
17550      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
17551        _this.volumeBar.removeClass('vjs-slider-active');
17552
17553        _this.removeClass('vjs-slider-active');
17554
17555        _this.trigger('sliderinactive');
17556      });
17557
17558      return _this;
17559    }
17560    /**
17561     * Create the `Component`'s DOM element
17562     *
17563     * @return {Element}
17564     *         The element that was created.
17565     */
17566
17567
17568    var _proto = VolumeControl.prototype;
17569
17570    _proto.createEl = function createEl() {
17571      var orientationClass = 'vjs-volume-horizontal';
17572
17573      if (this.options_.vertical) {
17574        orientationClass = 'vjs-volume-vertical';
17575      }
17576
17577      return _Component.prototype.createEl.call(this, 'div', {
17578        className: "vjs-volume-control vjs-control " + orientationClass
17579      });
17580    }
17581    /**
17582     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
17583     *
17584     * @param {EventTarget~Event} event
17585     *        `mousedown` or `touchstart` event that triggered this function
17586     *
17587     * @listens mousedown
17588     * @listens touchstart
17589     */
17590    ;
17591
17592    _proto.handleMouseDown = function handleMouseDown(event) {
17593      var doc = this.el_.ownerDocument;
17594      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
17595      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
17596      this.on(doc, 'mouseup', this.handleMouseUpHandler_);
17597      this.on(doc, 'touchend', this.handleMouseUpHandler_);
17598    }
17599    /**
17600     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
17601     *
17602     * @param {EventTarget~Event} event
17603     *        `mouseup` or `touchend` event that triggered this function.
17604     *
17605     * @listens touchend
17606     * @listens mouseup
17607     */
17608    ;
17609
17610    _proto.handleMouseUp = function handleMouseUp(event) {
17611      var doc = this.el_.ownerDocument;
17612      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
17613      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
17614      this.off(doc, 'mouseup', this.handleMouseUpHandler_);
17615      this.off(doc, 'touchend', this.handleMouseUpHandler_);
17616    }
17617    /**
17618     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
17619     *
17620     * @param {EventTarget~Event} event
17621     *        `mousedown` or `touchstart` event that triggered this function
17622     *
17623     * @listens mousedown
17624     * @listens touchstart
17625     */
17626    ;
17627
17628    _proto.handleMouseMove = function handleMouseMove(event) {
17629      this.volumeBar.handleMouseMove(event);
17630    };
17631
17632    return VolumeControl;
17633  }(Component$1);
17634  /**
17635   * Default options for the `VolumeControl`
17636   *
17637   * @type {Object}
17638   * @private
17639   */
17640
17641
17642  VolumeControl.prototype.options_ = {
17643    children: ['volumeBar']
17644  };
17645  Component$1.registerComponent('VolumeControl', VolumeControl);
17646
17647  /**
17648   * Check if muting volume is supported and if it isn't hide the mute toggle
17649   * button.
17650   *
17651   * @param {Component} self
17652   *        A reference to the mute toggle button
17653   *
17654   * @param {Player} player
17655   *        A reference to the player
17656   *
17657   * @private
17658   */
17659  var checkMuteSupport = function checkMuteSupport(self, player) {
17660    // hide mute toggle button if it's not supported by the current tech
17661    if (player.tech_ && !player.tech_.featuresMuteControl) {
17662      self.addClass('vjs-hidden');
17663    }
17664
17665    self.on(player, 'loadstart', function () {
17666      if (!player.tech_.featuresMuteControl) {
17667        self.addClass('vjs-hidden');
17668      } else {
17669        self.removeClass('vjs-hidden');
17670      }
17671    });
17672  };
17673
17674  /**
17675   * A button component for muting the audio.
17676   *
17677   * @extends Button
17678   */
17679
17680  var MuteToggle = /*#__PURE__*/function (_Button) {
17681    inheritsLoose(MuteToggle, _Button);
17682
17683    /**
17684     * Creates an instance of this class.
17685     *
17686     * @param {Player} player
17687     *        The `Player` that this class should be attached to.
17688     *
17689     * @param {Object} [options]
17690     *        The key/value store of player options.
17691     */
17692    function MuteToggle(player, options) {
17693      var _this;
17694
17695      _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
17696
17697      checkMuteSupport(assertThisInitialized(_this), player);
17698
17699      _this.on(player, ['loadstart', 'volumechange'], function (e) {
17700        return _this.update(e);
17701      });
17702
17703      return _this;
17704    }
17705    /**
17706     * Builds the default DOM `className`.
17707     *
17708     * @return {string}
17709     *         The DOM `className` for this object.
17710     */
17711
17712
17713    var _proto = MuteToggle.prototype;
17714
17715    _proto.buildCSSClass = function buildCSSClass() {
17716      return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
17717    }
17718    /**
17719     * This gets called when an `MuteToggle` is "clicked". See
17720     * {@link ClickableComponent} for more detailed information on what a click can be.
17721     *
17722     * @param {EventTarget~Event} [event]
17723     *        The `keydown`, `tap`, or `click` event that caused this function to be
17724     *        called.
17725     *
17726     * @listens tap
17727     * @listens click
17728     */
17729    ;
17730
17731    _proto.handleClick = function handleClick(event) {
17732      var vol = this.player_.volume();
17733      var lastVolume = this.player_.lastVolume_();
17734
17735      if (vol === 0) {
17736        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
17737        this.player_.volume(volumeToSet);
17738        this.player_.muted(false);
17739      } else {
17740        this.player_.muted(this.player_.muted() ? false : true);
17741      }
17742    }
17743    /**
17744     * Update the `MuteToggle` button based on the state of `volume` and `muted`
17745     * on the player.
17746     *
17747     * @param {EventTarget~Event} [event]
17748     *        The {@link Player#loadstart} event if this function was called
17749     *        through an event.
17750     *
17751     * @listens Player#loadstart
17752     * @listens Player#volumechange
17753     */
17754    ;
17755
17756    _proto.update = function update(event) {
17757      this.updateIcon_();
17758      this.updateControlText_();
17759    }
17760    /**
17761     * Update the appearance of the `MuteToggle` icon.
17762     *
17763     * Possible states (given `level` variable below):
17764     * - 0: crossed out
17765     * - 1: zero bars of volume
17766     * - 2: one bar of volume
17767     * - 3: two bars of volume
17768     *
17769     * @private
17770     */
17771    ;
17772
17773    _proto.updateIcon_ = function updateIcon_() {
17774      var vol = this.player_.volume();
17775      var level = 3; // in iOS when a player is loaded with muted attribute
17776      // and volume is changed with a native mute button
17777      // we want to make sure muted state is updated
17778
17779      if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
17780        this.player_.muted(this.player_.tech_.el_.muted);
17781      }
17782
17783      if (vol === 0 || this.player_.muted()) {
17784        level = 0;
17785      } else if (vol < 0.33) {
17786        level = 1;
17787      } else if (vol < 0.67) {
17788        level = 2;
17789      } // TODO improve muted icon classes
17790
17791
17792      for (var i = 0; i < 4; i++) {
17793        removeClass(this.el_, "vjs-vol-" + i);
17794      }
17795
17796      addClass(this.el_, "vjs-vol-" + level);
17797    }
17798    /**
17799     * If `muted` has changed on the player, update the control text
17800     * (`title` attribute on `vjs-mute-control` element and content of
17801     * `vjs-control-text` element).
17802     *
17803     * @private
17804     */
17805    ;
17806
17807    _proto.updateControlText_ = function updateControlText_() {
17808      var soundOff = this.player_.muted() || this.player_.volume() === 0;
17809      var text = soundOff ? 'Unmute' : 'Mute';
17810
17811      if (this.controlText() !== text) {
17812        this.controlText(text);
17813      }
17814    };
17815
17816    return MuteToggle;
17817  }(Button);
17818  /**
17819   * The text that should display over the `MuteToggle`s controls. Added for localization.
17820   *
17821   * @type {string}
17822   * @private
17823   */
17824
17825
17826  MuteToggle.prototype.controlText_ = 'Mute';
17827  Component$1.registerComponent('MuteToggle', MuteToggle);
17828
17829  /**
17830   * A Component to contain the MuteToggle and VolumeControl so that
17831   * they can work together.
17832   *
17833   * @extends Component
17834   */
17835
17836  var VolumePanel = /*#__PURE__*/function (_Component) {
17837    inheritsLoose(VolumePanel, _Component);
17838
17839    /**
17840     * Creates an instance of this class.
17841     *
17842     * @param {Player} player
17843     *        The `Player` that this class should be attached to.
17844     *
17845     * @param {Object} [options={}]
17846     *        The key/value store of player options.
17847     */
17848    function VolumePanel(player, options) {
17849      var _this;
17850
17851      if (options === void 0) {
17852        options = {};
17853      }
17854
17855      if (typeof options.inline !== 'undefined') {
17856        options.inline = options.inline;
17857      } else {
17858        options.inline = true;
17859      } // pass the inline option down to the VolumeControl as vertical if
17860      // the VolumeControl is on.
17861
17862
17863      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
17864        options.volumeControl = options.volumeControl || {};
17865        options.volumeControl.vertical = !options.inline;
17866      }
17867
17868      _this = _Component.call(this, player, options) || this; // this handler is used by mouse handler methods below
17869
17870      _this.handleKeyPressHandler_ = function (e) {
17871        return _this.handleKeyPress(e);
17872      };
17873
17874      _this.on(player, ['loadstart'], function (e) {
17875        return _this.volumePanelState_(e);
17876      });
17877
17878      _this.on(_this.muteToggle, 'keyup', function (e) {
17879        return _this.handleKeyPress(e);
17880      });
17881
17882      _this.on(_this.volumeControl, 'keyup', function (e) {
17883        return _this.handleVolumeControlKeyUp(e);
17884      });
17885
17886      _this.on('keydown', function (e) {
17887        return _this.handleKeyPress(e);
17888      });
17889
17890      _this.on('mouseover', function (e) {
17891        return _this.handleMouseOver(e);
17892      });
17893
17894      _this.on('mouseout', function (e) {
17895        return _this.handleMouseOut(e);
17896      }); // while the slider is active (the mouse has been pressed down and
17897      // is dragging) we do not want to hide the VolumeBar
17898
17899
17900      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
17901
17902      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
17903
17904      return _this;
17905    }
17906    /**
17907     * Add vjs-slider-active class to the VolumePanel
17908     *
17909     * @listens VolumeControl#slideractive
17910     * @private
17911     */
17912
17913
17914    var _proto = VolumePanel.prototype;
17915
17916    _proto.sliderActive_ = function sliderActive_() {
17917      this.addClass('vjs-slider-active');
17918    }
17919    /**
17920     * Removes vjs-slider-active class to the VolumePanel
17921     *
17922     * @listens VolumeControl#sliderinactive
17923     * @private
17924     */
17925    ;
17926
17927    _proto.sliderInactive_ = function sliderInactive_() {
17928      this.removeClass('vjs-slider-active');
17929    }
17930    /**
17931     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
17932     * depending on MuteToggle and VolumeControl state
17933     *
17934     * @listens Player#loadstart
17935     * @private
17936     */
17937    ;
17938
17939    _proto.volumePanelState_ = function volumePanelState_() {
17940      // hide volume panel if neither volume control or mute toggle
17941      // are displayed
17942      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
17943        this.addClass('vjs-hidden');
17944      } // if only mute toggle is visible we don't want
17945      // volume panel expanding when hovered or active
17946
17947
17948      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
17949        this.addClass('vjs-mute-toggle-only');
17950      }
17951    }
17952    /**
17953     * Create the `Component`'s DOM element
17954     *
17955     * @return {Element}
17956     *         The element that was created.
17957     */
17958    ;
17959
17960    _proto.createEl = function createEl() {
17961      var orientationClass = 'vjs-volume-panel-horizontal';
17962
17963      if (!this.options_.inline) {
17964        orientationClass = 'vjs-volume-panel-vertical';
17965      }
17966
17967      return _Component.prototype.createEl.call(this, 'div', {
17968        className: "vjs-volume-panel vjs-control " + orientationClass
17969      });
17970    }
17971    /**
17972     * Dispose of the `volume-panel` and all child components.
17973     */
17974    ;
17975
17976    _proto.dispose = function dispose() {
17977      this.handleMouseOut();
17978
17979      _Component.prototype.dispose.call(this);
17980    }
17981    /**
17982     * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
17983     * the volume panel and sets focus on `MuteToggle`.
17984     *
17985     * @param {EventTarget~Event} event
17986     *        The `keyup` event that caused this function to be called.
17987     *
17988     * @listens keyup
17989     */
17990    ;
17991
17992    _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) {
17993      if (keycode.isEventKey(event, 'Esc')) {
17994        this.muteToggle.focus();
17995      }
17996    }
17997    /**
17998     * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
17999     * Turns on listening for `mouseover` event. When they happen it
18000     * calls `this.handleMouseOver`.
18001     *
18002     * @param {EventTarget~Event} event
18003     *        The `mouseover` event that caused this function to be called.
18004     *
18005     * @listens mouseover
18006     */
18007    ;
18008
18009    _proto.handleMouseOver = function handleMouseOver(event) {
18010      this.addClass('vjs-hover');
18011      on(document_1, 'keyup', this.handleKeyPressHandler_);
18012    }
18013    /**
18014     * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
18015     * Turns on listening for `mouseout` event. When they happen it
18016     * calls `this.handleMouseOut`.
18017     *
18018     * @param {EventTarget~Event} event
18019     *        The `mouseout` event that caused this function to be called.
18020     *
18021     * @listens mouseout
18022     */
18023    ;
18024
18025    _proto.handleMouseOut = function handleMouseOut(event) {
18026      this.removeClass('vjs-hover');
18027      off(document_1, 'keyup', this.handleKeyPressHandler_);
18028    }
18029    /**
18030     * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
18031     * looking for ESC, which hides the `VolumeControl`.
18032     *
18033     * @param {EventTarget~Event} event
18034     *        The keypress that triggered this event.
18035     *
18036     * @listens keydown | keyup
18037     */
18038    ;
18039
18040    _proto.handleKeyPress = function handleKeyPress(event) {
18041      if (keycode.isEventKey(event, 'Esc')) {
18042        this.handleMouseOut();
18043      }
18044    };
18045
18046    return VolumePanel;
18047  }(Component$1);
18048  /**
18049   * Default options for the `VolumeControl`
18050   *
18051   * @type {Object}
18052   * @private
18053   */
18054
18055
18056  VolumePanel.prototype.options_ = {
18057    children: ['muteToggle', 'volumeControl']
18058  };
18059  Component$1.registerComponent('VolumePanel', VolumePanel);
18060
18061  /**
18062   * The Menu component is used to build popup menus, including subtitle and
18063   * captions selection menus.
18064   *
18065   * @extends Component
18066   */
18067
18068  var Menu = /*#__PURE__*/function (_Component) {
18069    inheritsLoose(Menu, _Component);
18070
18071    /**
18072     * Create an instance of this class.
18073     *
18074     * @param {Player} player
18075     *        the player that this component should attach to
18076     *
18077     * @param {Object} [options]
18078     *        Object of option names and values
18079     *
18080     */
18081    function Menu(player, options) {
18082      var _this;
18083
18084      _this = _Component.call(this, player, options) || this;
18085
18086      if (options) {
18087        _this.menuButton_ = options.menuButton;
18088      }
18089
18090      _this.focusedChild_ = -1;
18091
18092      _this.on('keydown', function (e) {
18093        return _this.handleKeyDown(e);
18094      }); // All the menu item instances share the same blur handler provided by the menu container.
18095
18096
18097      _this.boundHandleBlur_ = function (e) {
18098        return _this.handleBlur(e);
18099      };
18100
18101      _this.boundHandleTapClick_ = function (e) {
18102        return _this.handleTapClick(e);
18103      };
18104
18105      return _this;
18106    }
18107    /**
18108     * Add event listeners to the {@link MenuItem}.
18109     *
18110     * @param {Object} component
18111     *        The instance of the `MenuItem` to add listeners to.
18112     *
18113     */
18114
18115
18116    var _proto = Menu.prototype;
18117
18118    _proto.addEventListenerForItem = function addEventListenerForItem(component) {
18119      if (!(component instanceof Component$1)) {
18120        return;
18121      }
18122
18123      this.on(component, 'blur', this.boundHandleBlur_);
18124      this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
18125    }
18126    /**
18127     * Remove event listeners from the {@link MenuItem}.
18128     *
18129     * @param {Object} component
18130     *        The instance of the `MenuItem` to remove listeners.
18131     *
18132     */
18133    ;
18134
18135    _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
18136      if (!(component instanceof Component$1)) {
18137        return;
18138      }
18139
18140      this.off(component, 'blur', this.boundHandleBlur_);
18141      this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
18142    }
18143    /**
18144     * This method will be called indirectly when the component has been added
18145     * before the component adds to the new menu instance by `addItem`.
18146     * In this case, the original menu instance will remove the component
18147     * by calling `removeChild`.
18148     *
18149     * @param {Object} component
18150     *        The instance of the `MenuItem`
18151     */
18152    ;
18153
18154    _proto.removeChild = function removeChild(component) {
18155      if (typeof component === 'string') {
18156        component = this.getChild(component);
18157      }
18158
18159      this.removeEventListenerForItem(component);
18160
18161      _Component.prototype.removeChild.call(this, component);
18162    }
18163    /**
18164     * Add a {@link MenuItem} to the menu.
18165     *
18166     * @param {Object|string} component
18167     *        The name or instance of the `MenuItem` to add.
18168     *
18169     */
18170    ;
18171
18172    _proto.addItem = function addItem(component) {
18173      var childComponent = this.addChild(component);
18174
18175      if (childComponent) {
18176        this.addEventListenerForItem(childComponent);
18177      }
18178    }
18179    /**
18180     * Create the `Menu`s DOM element.
18181     *
18182     * @return {Element}
18183     *         the element that was created
18184     */
18185    ;
18186
18187    _proto.createEl = function createEl$1() {
18188      var contentElType = this.options_.contentElType || 'ul';
18189      this.contentEl_ = createEl(contentElType, {
18190        className: 'vjs-menu-content'
18191      });
18192      this.contentEl_.setAttribute('role', 'menu');
18193
18194      var el = _Component.prototype.createEl.call(this, 'div', {
18195        append: this.contentEl_,
18196        className: 'vjs-menu'
18197      });
18198
18199      el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
18200      // where a click on the parent is significant
18201      on(el, 'click', function (event) {
18202        var target = (window.$(event.target).hasClass('vjs-menu-item'))? window.$(event.target):window.$(event.target).parents('.vjs-menu-item').first();
18203        if(window.$(event.target).parents('.vjs-subs-caps-button').length > 0 && target.length>0){
18204          window.$('button.vjs-subs-caps-button')[0].innerHTML = window._subtitleList.find(subtitle => subtitle.language.toLowerCase() == (target.data('label').toLowerCase() || 'off')).icon;
18205          window.$('.vjs-subs-caps-button .vjs-menu-item').removeClass('vjs-selected');
18206          target.addClass('vjs-selected');
18207        }
18208        event.preventDefault();
18209        event.stopImmediatePropagation();
18210      });
18211
18212      on(el, 'touchend', function (event) {
18213        var target = (window.$(event.target).hasClass('vjs-menu-item'))? window.$(event.target):window.$(event.target).parents('.vjs-menu-item').first();
18214        if(window.$(event.target).parents('.vjs-subs-caps-button').length > 0 && target.length>0){
18215          window.$('button.vjs-subs-caps-button')[0].innerHTML = window._subtitleList.find(subtitle => subtitle.language.toLowerCase() == (target.data('label').toLowerCase() || 'off')).icon;
18216          window.$('.vjs-subs-caps-button .vjs-menu-item').removeClass('vjs-selected');
18217          target.addClass('vjs-selected');
18218        }
18219        event.preventDefault();
18220        event.stopImmediatePropagation();
18221      });
18222      return el;
18223    };
18224
18225    _proto.dispose = function dispose() {
18226      this.contentEl_ = null;
18227      this.boundHandleBlur_ = null;
18228      this.boundHandleTapClick_ = null;
18229
18230      _Component.prototype.dispose.call(this);
18231    }
18232    /**
18233     * Called when a `MenuItem` loses focus.
18234     *
18235     * @param {EventTarget~Event} event
18236     *        The `blur` event that caused this function to be called.
18237     *
18238     * @listens blur
18239     */
18240    ;
18241
18242    _proto.handleBlur = function handleBlur(event) {
18243      var relatedTarget = event.relatedTarget || document_1.activeElement; // Close menu popup when a user clicks outside the menu
18244
18245      if (!this.children().some(function (element) {
18246        return element.el() === relatedTarget;
18247      })) {
18248        var btn = this.menuButton_;
18249
18250        if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
18251          btn.unpressButton();
18252        }
18253      }
18254    }
18255    /**
18256     * Called when a `MenuItem` gets clicked or tapped.
18257     *
18258     * @param {EventTarget~Event} event
18259     *        The `click` or `tap` event that caused this function to be called.
18260     *
18261     * @listens click,tap
18262     */
18263    ;
18264
18265    _proto.handleTapClick = function handleTapClick(event) {
18266      // Unpress the associated MenuButton, and move focus back to it
18267      if (this.menuButton_) {
18268        this.menuButton_.unpressButton();
18269        var childComponents = this.children();
18270
18271        if (!Array.isArray(childComponents)) {
18272          return;
18273        }
18274
18275        var foundComponent = childComponents.filter(function (component) {
18276          return component.el() === event.target;
18277        })[0];
18278
18279        if (!foundComponent) {
18280          return;
18281        } // don't focus menu button if item is a caption settings item
18282        // because focus will move elsewhere
18283
18284
18285        if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
18286          this.menuButton_.focus();
18287        }
18288      }
18289    }
18290    /**
18291     * Handle a `keydown` event on this menu. This listener is added in the constructor.
18292     *
18293     * @param {EventTarget~Event} event
18294     *        A `keydown` event that happened on the menu.
18295     *
18296     * @listens keydown
18297     */
18298    ;
18299
18300    _proto.handleKeyDown = function handleKeyDown(event) {
18301      // Left and Down Arrows
18302      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
18303        event.preventDefault();
18304        event.stopPropagation();
18305        this.stepForward(); // Up and Right Arrows
18306      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
18307        event.preventDefault();
18308        event.stopPropagation();
18309        this.stepBack();
18310      }
18311    }
18312    /**
18313     * Move to next (lower) menu item for keyboard users.
18314     */
18315    ;
18316
18317    _proto.stepForward = function stepForward() {
18318      var stepChild = 0;
18319
18320      if (this.focusedChild_ !== undefined) {
18321        stepChild = this.focusedChild_ + 1;
18322      }
18323
18324      this.focus(stepChild);
18325    }
18326    /**
18327     * Move to previous (higher) menu item for keyboard users.
18328     */
18329    ;
18330
18331    _proto.stepBack = function stepBack() {
18332      var stepChild = 0;
18333
18334      if (this.focusedChild_ !== undefined) {
18335        stepChild = this.focusedChild_ - 1;
18336      }
18337
18338      this.focus(stepChild);
18339    }
18340    /**
18341     * Set focus on a {@link MenuItem} in the `Menu`.
18342     *
18343     * @param {Object|string} [item=0]
18344     *        Index of child item set focus on.
18345     */
18346    ;
18347
18348    _proto.focus = function focus(item) {
18349      if (item === void 0) {
18350        item = 0;
18351      }
18352
18353      var children = this.children().slice();
18354      var haveTitle = children.length && children[0].hasClass('vjs-menu-title');
18355
18356      if (haveTitle) {
18357        children.shift();
18358      }
18359
18360      if (children.length > 0) {
18361        if (item < 0) {
18362          item = 0;
18363        } else if (item >= children.length) {
18364          item = children.length - 1;
18365        }
18366
18367        this.focusedChild_ = item;
18368        children[item].el_.focus();
18369      }
18370    };
18371
18372    return Menu;
18373  }(Component$1);
18374
18375  Component$1.registerComponent('Menu', Menu);
18376
18377  /**
18378   * A `MenuButton` class for any popup {@link Menu}.
18379   *
18380   * @extends Component
18381   */
18382
18383  var MenuButton = /*#__PURE__*/function (_Component) {
18384    inheritsLoose(MenuButton, _Component);
18385
18386    /**
18387     * Creates an instance of this class.
18388     *
18389     * @param {Player} player
18390     *        The `Player` that this class should be attached to.
18391     *
18392     * @param {Object} [options={}]
18393     *        The key/value store of player options.
18394     */
18395    function MenuButton(player, options) {
18396      var _this;
18397
18398      if (options === void 0) {
18399        options = {};
18400      }
18401
18402      _this = _Component.call(this, player, options) || this;
18403      _this.menuButton_ = new Button(player, options);
18404
18405      _this.menuButton_.controlText(_this.controlText_);
18406
18407      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
18408
18409
18410      var buttonClass = Button.prototype.buildCSSClass();
18411      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
18412
18413      _this.menuButton_.removeClass('vjs-control');
18414
18415      _this.addChild(_this.menuButton_);
18416
18417      _this.update();
18418
18419      _this.enabled_ = true;
18420
18421      var handleClick = function handleClick(e) {
18422        return _this.handleClick(e);
18423      };
18424
18425      _this.handleMenuKeyUp_ = function (e) {
18426        return _this.handleMenuKeyUp(e);
18427      };
18428
18429      _this.on(_this.menuButton_, 'tap', handleClick);
18430
18431      _this.on(_this.menuButton_, 'click', handleClick);
18432
18433      _this.on(_this.menuButton_, 'keydown', function (e) {
18434        return _this.handleKeyDown(e);
18435      });
18436
18437      _this.on(_this.menuButton_, 'mouseenter', function () {
18438        _this.addClass('vjs-hover');
18439
18440        _this.menu.show();
18441
18442        on(document_1, 'keyup', _this.handleMenuKeyUp_);
18443      });
18444
18445      _this.on('mouseleave', function (e) {
18446        return _this.handleMouseLeave(e);
18447      });
18448
18449      _this.on('keydown', function (e) {
18450        return _this.handleSubmenuKeyDown(e);
18451      });
18452
18453      return _this;
18454    }
18455    /**
18456     * Update the menu based on the current state of its items.
18457     */
18458
18459
18460    var _proto = MenuButton.prototype;
18461
18462    _proto.update = function update() {
18463      var menu = this.createMenu();
18464
18465      if (this.menu) {
18466        this.menu.dispose();
18467        this.removeChild(this.menu);
18468      }
18469
18470      this.menu = menu;
18471      this.addChild(menu);
18472      /**
18473       * Track the state of the menu button
18474       *
18475       * @type {Boolean}
18476       * @private
18477       */
18478
18479      this.buttonPressed_ = false;
18480      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
18481
18482      if (this.items && this.items.length <= this.hideThreshold_) {
18483        this.hide();
18484      } else {
18485        this.show();
18486      }
18487    }
18488    /**
18489     * Create the menu and add all items to it.
18490     *
18491     * @return {Menu}
18492     *         The constructed menu
18493     */
18494    ;
18495
18496    _proto.createMenu = function createMenu() {
18497      var menu = new Menu(this.player_, {
18498        menuButton: this
18499      });
18500      /**
18501       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
18502       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
18503       * it here because every time we run `createMenu` we need to reset the value.
18504       *
18505       * @protected
18506       * @type {Number}
18507       */
18508
18509      this.hideThreshold_ = 0; // Add a title list item to the top
18510
18511      if (this.options_.title) {
18512        var titleEl = createEl('li', {
18513          className: 'vjs-menu-title',
18514          textContent: toTitleCase$1(this.options_.title),
18515          tabIndex: -1
18516        });
18517        var titleComponent = new Component$1(this.player_, {
18518          el: titleEl
18519        });
18520        menu.addItem(titleComponent);
18521      }
18522
18523      this.items = this.createItems();
18524
18525      if (this.items) {
18526        // Add menu items to the menu
18527        for (var i = 0; i < this.items.length; i++) {
18528          menu.addItem(this.items[i]);
18529        }
18530      }
18531
18532      return menu;
18533    }
18534    /**
18535     * Create the list of menu items. Specific to each subclass.
18536     *
18537     * @abstract
18538     */
18539    ;
18540
18541    _proto.createItems = function createItems() {}
18542    /**
18543     * Create the `MenuButtons`s DOM element.
18544     *
18545     * @return {Element}
18546     *         The element that gets created.
18547     */
18548    ;
18549
18550    _proto.createEl = function createEl() {
18551      return _Component.prototype.createEl.call(this, 'div', {
18552        className: this.buildWrapperCSSClass()
18553      }, {});
18554    }
18555    /**
18556     * Allow sub components to stack CSS class names for the wrapper element
18557     *
18558     * @return {string}
18559     *         The constructed wrapper DOM `className`
18560     */
18561    ;
18562
18563    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18564      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
18565
18566      if (this.options_.inline === true) {
18567        menuButtonClass += '-inline';
18568      } else {
18569        menuButtonClass += '-popup';
18570      } // TODO: Fix the CSS so that this isn't necessary
18571
18572
18573      var buttonClass = Button.prototype.buildCSSClass();
18574      return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
18575    }
18576    /**
18577     * Builds the default DOM `className`.
18578     *
18579     * @return {string}
18580     *         The DOM `className` for this object.
18581     */
18582    ;
18583
18584    _proto.buildCSSClass = function buildCSSClass() {
18585      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
18586
18587      if (this.options_.inline === true) {
18588        menuButtonClass += '-inline';
18589      } else {
18590        menuButtonClass += '-popup';
18591      }
18592
18593      return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
18594    }
18595    /**
18596     * Get or set the localized control text that will be used for accessibility.
18597     *
18598     * > NOTE: This will come from the internal `menuButton_` element.
18599     *
18600     * @param {string} [text]
18601     *        Control text for element.
18602     *
18603     * @param {Element} [el=this.menuButton_.el()]
18604     *        Element to set the title on.
18605     *
18606     * @return {string}
18607     *         - The control text when getting
18608     */
18609    ;
18610
18611    _proto.controlText = function controlText(text, el) {
18612      if (el === void 0) {
18613        el = this.menuButton_.el();
18614      }
18615
18616      return this.menuButton_.controlText(text, el);
18617    }
18618    /**
18619     * Dispose of the `menu-button` and all child components.
18620     */
18621    ;
18622
18623    _proto.dispose = function dispose() {
18624      this.handleMouseLeave();
18625
18626      _Component.prototype.dispose.call(this);
18627    }
18628    /**
18629     * Handle a click on a `MenuButton`.
18630     * See {@link ClickableComponent#handleClick} for instances where this is called.
18631     *
18632     * @param {EventTarget~Event} event
18633     *        The `keydown`, `tap`, or `click` event that caused this function to be
18634     *        called.
18635     *
18636     * @listens tap
18637     * @listens click
18638     */
18639    ;
18640
18641    _proto.handleClick = function handleClick(event) {
18642      if (this.buttonPressed_) {
18643        this.unpressButton();
18644      } else {
18645        this.pressButton();
18646      }
18647    }
18648    /**
18649     * Handle `mouseleave` for `MenuButton`.
18650     *
18651     * @param {EventTarget~Event} event
18652     *        The `mouseleave` event that caused this function to be called.
18653     *
18654     * @listens mouseleave
18655     */
18656    ;
18657
18658    _proto.handleMouseLeave = function handleMouseLeave(event) {
18659      this.removeClass('vjs-hover');
18660      off(document_1, 'keyup', this.handleMenuKeyUp_);
18661    }
18662    /**
18663     * Set the focus to the actual button, not to this element
18664     */
18665    ;
18666
18667    _proto.focus = function focus() {
18668      this.menuButton_.focus();
18669    }
18670    /**
18671     * Remove the focus from the actual button, not this element
18672     */
18673    ;
18674
18675    _proto.blur = function blur() {
18676      this.menuButton_.blur();
18677    }
18678    /**
18679     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
18680     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
18681     *
18682     * @param {EventTarget~Event} event
18683     *        The `keydown` event that caused this function to be called.
18684     *
18685     * @listens keydown
18686     */
18687    ;
18688
18689    _proto.handleKeyDown = function handleKeyDown(event) {
18690      // Escape or Tab unpress the 'button'
18691      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
18692        if (this.buttonPressed_) {
18693          this.unpressButton();
18694        } // Don't preventDefault for Tab key - we still want to lose focus
18695
18696
18697        if (!keycode.isEventKey(event, 'Tab')) {
18698          event.preventDefault(); // Set focus back to the menu button's button
18699
18700          this.menuButton_.focus();
18701        } // Up Arrow or Down Arrow also 'press' the button to open the menu
18702
18703      } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
18704        if (!this.buttonPressed_) {
18705          event.preventDefault();
18706          this.pressButton();
18707        }
18708      }
18709    }
18710    /**
18711     * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
18712     * the constructor.
18713     *
18714     * @param {EventTarget~Event} event
18715     *        Key press event
18716     *
18717     * @listens keyup
18718     */
18719    ;
18720
18721    _proto.handleMenuKeyUp = function handleMenuKeyUp(event) {
18722      // Escape hides popup menu
18723      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
18724        this.removeClass('vjs-hover');
18725      }
18726    }
18727    /**
18728     * This method name now delegates to `handleSubmenuKeyDown`. This means
18729     * anyone calling `handleSubmenuKeyPress` will not see their method calls
18730     * stop working.
18731     *
18732     * @param {EventTarget~Event} event
18733     *        The event that caused this function to be called.
18734     */
18735    ;
18736
18737    _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
18738      this.handleSubmenuKeyDown(event);
18739    }
18740    /**
18741     * Handle a `keydown` event on a sub-menu. The listener for this is added in
18742     * the constructor.
18743     *
18744     * @param {EventTarget~Event} event
18745     *        Key press event
18746     *
18747     * @listens keydown
18748     */
18749    ;
18750
18751    _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
18752      // Escape or Tab unpress the 'button'
18753      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
18754        if (this.buttonPressed_) {
18755          this.unpressButton();
18756        } // Don't preventDefault for Tab key - we still want to lose focus
18757
18758
18759        if (!keycode.isEventKey(event, 'Tab')) {
18760          event.preventDefault(); // Set focus back to the menu button's button
18761
18762          this.menuButton_.focus();
18763        }
18764      }
18765    }
18766    /**
18767     * Put the current `MenuButton` into a pressed state.
18768     */
18769    ;
18770
18771    _proto.pressButton = function pressButton() {
18772      if (this.enabled_) {
18773        this.buttonPressed_ = true;
18774        this.menu.show();
18775        this.menu.lockShowing();
18776        this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
18777        // undesired scrolling behavior when the player is in an iframe
18778
18779        if (IS_IOS && isInFrame()) {
18780          // Return early so that the menu isn't focused
18781          return;
18782        }
18783
18784        this.menu.focus();
18785      }
18786    }
18787    /**
18788     * Take the current `MenuButton` out of a pressed state.
18789     */
18790    ;
18791
18792    _proto.unpressButton = function unpressButton() {
18793      if (this.enabled_) {
18794        this.buttonPressed_ = false;
18795        this.menu.unlockShowing();
18796        this.menu.hide();
18797        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
18798      }
18799    }
18800    /**
18801     * Disable the `MenuButton`. Don't allow it to be clicked.
18802     */
18803    ;
18804
18805    _proto.disable = function disable() {
18806      this.unpressButton();
18807      this.enabled_ = false;
18808      this.addClass('vjs-disabled');
18809      this.menuButton_.disable();
18810    }
18811    /**
18812     * Enable the `MenuButton`. Allow it to be clicked.
18813     */
18814    ;
18815
18816    _proto.enable = function enable() {
18817      this.enabled_ = true;
18818      this.removeClass('vjs-disabled');
18819      this.menuButton_.enable();
18820    };
18821
18822    return MenuButton;
18823  }(Component$1);
18824
18825  Component$1.registerComponent('MenuButton', MenuButton);
18826
18827  /**
18828   * The base class for buttons that toggle specific  track types (e.g. subtitles).
18829   *
18830   * @extends MenuButton
18831   */
18832
18833  var TrackButton = /*#__PURE__*/function (_MenuButton) {
18834    inheritsLoose(TrackButton, _MenuButton);
18835
18836    /**
18837     * Creates an instance of this class.
18838     *
18839     * @param {Player} player
18840     *        The `Player` that this class should be attached to.
18841     *
18842     * @param {Object} [options]
18843     *        The key/value store of player options.
18844     */
18845    function TrackButton(player, options) {
18846      var _this;
18847
18848      var tracks = options.tracks;
18849      _this = _MenuButton.call(this, player, options) || this;
18850
18851      if (_this.items.length <= 1) {
18852        _this.hide();
18853      }
18854
18855      if (!tracks) {
18856        return assertThisInitialized(_this);
18857      }
18858
18859      var updateHandler = bind(assertThisInitialized(_this), _this.update);
18860      tracks.addEventListener('removetrack', updateHandler);
18861      tracks.addEventListener('addtrack', updateHandler);
18862      tracks.addEventListener('labelchange', updateHandler);
18863
18864      _this.player_.on('ready', updateHandler);
18865
18866      _this.player_.on('dispose', function () {
18867        tracks.removeEventListener('removetrack', updateHandler);
18868        tracks.removeEventListener('addtrack', updateHandler);
18869        tracks.removeEventListener('labelchange', updateHandler);
18870      });
18871
18872      return _this;
18873    }
18874
18875    return TrackButton;
18876  }(MenuButton);
18877
18878  Component$1.registerComponent('TrackButton', TrackButton);
18879
18880  /**
18881   * @file menu-keys.js
18882   */
18883
18884  /**
18885    * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
18886    * Note that 'Enter' and 'Space' are not included here (otherwise they would
18887    * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
18888    * @typedef MenuKeys
18889    * @array
18890    */
18891  var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
18892
18893  /**
18894   * The component for a menu item. `<li>`
18895   *
18896   * @extends ClickableComponent
18897   */
18898
18899  var MenuItem = /*#__PURE__*/function (_ClickableComponent) {
18900    inheritsLoose(MenuItem, _ClickableComponent);
18901
18902    /**
18903     * Creates an instance of the this class.
18904     *
18905     * @param {Player} player
18906     *        The `Player` that this class should be attached to.
18907     *
18908     * @param {Object} [options={}]
18909     *        The key/value store of player options.
18910     *
18911     */
18912    function MenuItem(player, options) {
18913      var _this;
18914
18915      _this = _ClickableComponent.call(this, player, options) || this;
18916      _this.selectable = options.selectable;
18917      _this.isSelected_ = options.selected || false;
18918      _this.multiSelectable = options.multiSelectable;
18919
18920      _this.selected(_this.isSelected_);
18921
18922      if (_this.selectable) {
18923        if (_this.multiSelectable) {
18924          _this.el_.setAttribute('role', 'menuitemcheckbox');
18925        } else {
18926          _this.el_.setAttribute('role', 'menuitemradio');
18927        }
18928      } else {
18929        _this.el_.setAttribute('role', 'menuitem');
18930      }
18931
18932      return _this;
18933    }
18934    /**
18935     * Create the `MenuItem's DOM element
18936     *
18937     * @param {string} [type=li]
18938     *        Element's node type, not actually used, always set to `li`.
18939     *
18940     * @param {Object} [props={}]
18941     *        An object of properties that should be set on the element
18942     *
18943     * @param {Object} [attrs={}]
18944     *        An object of attributes that should be set on the element
18945     *
18946     * @return {Element}
18947     *         The element that gets created.
18948     */
18949
18950
18951    var _proto = MenuItem.prototype;
18952
18953    _proto.createEl = function createEl$1(type, props, attrs) {
18954      // The control is textual, not just an icon
18955      this.nonIconControl = true;
18956
18957      var el = _ClickableComponent.prototype.createEl.call(this, 'li', assign({
18958        className: 'vjs-menu-item',
18959        tabIndex: -1
18960      }, props), attrs); // swap icon with menu item text.
18961
18962
18963      el.replaceChild(createEl('span', {
18964        className: 'vjs-menu-item-text',
18965        textContent: this.localize(this.options_.label)
18966      }), el.querySelector('.vjs-icon-placeholder'));
18967
18968      el.setAttribute("data-label", this?.options_?.label);
18969
18970      return el;
18971    }
18972    /**
18973     * Ignore keys which are used by the menu, but pass any other ones up. See
18974     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
18975     *
18976     * @param {EventTarget~Event} event
18977     *        The `keydown` event that caused this function to be called.
18978     *
18979     * @listens keydown
18980     */
18981    ;
18982
18983    _proto.handleKeyDown = function handleKeyDown(event) {
18984      if (!MenuKeys.some(function (key) {
18985        return keycode.isEventKey(event, key);
18986      })) {
18987        // Pass keydown handling up for unused keys
18988        _ClickableComponent.prototype.handleKeyDown.call(this, event);
18989      }
18990    }
18991    /**
18992     * Any click on a `MenuItem` puts it into the selected state.
18993     * See {@link ClickableComponent#handleClick} for instances where this is called.
18994     *
18995     * @param {EventTarget~Event} event
18996     *        The `keydown`, `tap`, or `click` event that caused this function to be
18997     *        called.
18998     *
18999     * @listens tap
19000     * @listens click
19001     */
19002    ;
19003
19004    _proto.handleClick = function handleClick(event) {
19005      this.selected(true);
19006    }
19007    /**
19008     * Set the state for this menu item as selected or not.
19009     *
19010     * @param {boolean} selected
19011     *        if the menu item is selected or not
19012     */
19013    ;
19014
19015    _proto.selected = function selected(_selected) {
19016      if (this.selectable) {
19017        if (_selected) {
19018          this.addClass('vjs-selected');
19019          this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
19020          // so indicate selected state to screen reader in the control text.
19021
19022          this.controlText(', selected');
19023          this.isSelected_ = true;
19024        } else {
19025          this.removeClass('vjs-selected');
19026          this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
19027
19028          this.controlText('');
19029          this.isSelected_ = false;
19030        }
19031      }
19032    };
19033
19034    return MenuItem;
19035  }(ClickableComponent);
19036
19037  Component$1.registerComponent('MenuItem', MenuItem);
19038
19039  /**
19040   * The specific menu item type for selecting a language within a text track kind
19041   *
19042   * @extends MenuItem
19043   */
19044
19045  var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
19046    inheritsLoose(TextTrackMenuItem, _MenuItem);
19047
19048    /**
19049     * Creates an instance of this class.
19050     *
19051     * @param {Player} player
19052     *        The `Player` that this class should be attached to.
19053     *
19054     * @param {Object} [options]
19055     *        The key/value store of player options.
19056     */
19057    function TextTrackMenuItem(player, options) {
19058      var _this;
19059
19060      var track = options.track;
19061      var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
19062
19063      options.label = track.label || track.language || 'Unknown';
19064      options.selected = track.mode === 'showing';
19065      _this = _MenuItem.call(this, player, options) || this;
19066      _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
19067      // out empty kinds.
19068
19069      _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
19070
19071      var changeHandler = function changeHandler() {
19072        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
19073          args[_key] = arguments[_key];
19074        }
19075
19076        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
19077      };
19078
19079      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
19080        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
19081          args[_key2] = arguments[_key2];
19082        }
19083
19084        _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args);
19085      };
19086
19087      player.on(['loadstart', 'texttrackchange'], changeHandler);
19088      tracks.addEventListener('change', changeHandler);
19089      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
19090
19091      _this.on('dispose', function () {
19092        player.off(['loadstart', 'texttrackchange'], changeHandler);
19093        tracks.removeEventListener('change', changeHandler);
19094        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
19095      }); // iOS7 doesn't dispatch change events to TextTrackLists when an
19096      // associated track's mode changes. Without something like
19097      // Object.observe() (also not present on iOS7), it's not
19098      // possible to detect changes to the mode attribute and polyfill
19099      // the change event. As a poor substitute, we manually dispatch
19100      // change events whenever the controls modify the mode.
19101
19102
19103      if (tracks.onchange === undefined) {
19104        var event;
19105
19106        _this.on(['tap', 'click'], function () {
19107          if (typeof window_1.Event !== 'object') {
19108            // Android 2.3 throws an Illegal Constructor error for window.Event
19109            try {
19110              event = new window_1.Event('change');
19111            } catch (err) {// continue regardless of error
19112            }
19113          }
19114
19115          if (!event) {
19116            event = document_1.createEvent('Event');
19117            event.initEvent('change', true, true);
19118          }
19119
19120          tracks.dispatchEvent(event);
19121        });
19122      } // set the default state based on current tracks
19123
19124
19125      _this.handleTracksChange();
19126
19127      return _this;
19128    }
19129    /**
19130     * This gets called when an `TextTrackMenuItem` is "clicked". See
19131     * {@link ClickableComponent} for more detailed information on what a click can be.
19132     *
19133     * @param {EventTarget~Event} event
19134     *        The `keydown`, `tap`, or `click` event that caused this function to be
19135     *        called.
19136     *
19137     * @listens tap
19138     * @listens click
19139     */
19140
19141
19142    var _proto = TextTrackMenuItem.prototype;
19143
19144    _proto.handleClick = function handleClick(event) {
19145      var referenceTrack = this.track;
19146      var tracks = this.player_.textTracks();
19147
19148      _MenuItem.prototype.handleClick.call(this, event);
19149
19150      if (!tracks) {
19151        return;
19152      }
19153
19154      for (var i = 0; i < tracks.length; i++) {
19155        var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
19156        // skip it. We do not want to affect tracks of incompatible kind(s).
19157
19158        if (this.kinds.indexOf(track.kind) === -1) {
19159          continue;
19160        } // If this text track is the component's track and it is not showing,
19161        // set it to showing.
19162
19163
19164        if (track === referenceTrack) {
19165          if (track.mode !== 'showing') {
19166            track.mode = 'showing';
19167          } // If this text track is not the component's track and it is not
19168          // disabled, set it to disabled.
19169
19170        } else if (track.mode !== 'disabled') {
19171          track.mode = 'disabled';
19172        }
19173      }
19174    }
19175    /**
19176     * Handle text track list change
19177     *
19178     * @param {EventTarget~Event} event
19179     *        The `change` event that caused this function to be called.
19180     *
19181     * @listens TextTrackList#change
19182     */
19183    ;
19184
19185    _proto.handleTracksChange = function handleTracksChange(event) {
19186      var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
19187      // screen readers to read the appended control text unnecessarily
19188
19189      if (shouldBeSelected !== this.isSelected_) {
19190        this.selected(shouldBeSelected);
19191      }
19192    };
19193
19194    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
19195      if (this.track.mode === 'showing') {
19196        var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
19197
19198        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
19199          return;
19200        }
19201
19202        this.player_.cache_.selectedLanguage = {
19203          enabled: true,
19204          language: this.track.language,
19205          kind: this.track.kind
19206        };
19207      }
19208    };
19209
19210    _proto.dispose = function dispose() {
19211      // remove reference to track object on dispose
19212      this.track = null;
19213
19214      _MenuItem.prototype.dispose.call(this);
19215    };
19216
19217    return TextTrackMenuItem;
19218  }(MenuItem);
19219
19220  Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
19221
19222  /**
19223   * A special menu item for turning of a specific type of text track
19224   *
19225   * @extends TextTrackMenuItem
19226   */
19227
19228  var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
19229    inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
19230
19231    /**
19232     * Creates an instance of this class.
19233     *
19234     * @param {Player} player
19235     *        The `Player` that this class should be attached to.
19236     *
19237     * @param {Object} [options]
19238     *        The key/value store of player options.
19239     */
19240    function OffTextTrackMenuItem(player, options) {
19241      // Create pseudo track info
19242      // Requires options['kind']
19243      options.track = {
19244        player: player,
19245        // it is no longer necessary to store `kind` or `kinds` on the track itself
19246        // since they are now stored in the `kinds` property of all instances of
19247        // TextTrackMenuItem, but this will remain for backwards compatibility
19248        kind: options.kind,
19249        kinds: options.kinds,
19250        "default": false,
19251        mode: 'disabled'
19252      };
19253
19254      if (!options.kinds) {
19255        options.kinds = [options.kind];
19256      }
19257
19258      if (options.label) {
19259        options.track.label = options.label;
19260      } else {
19261        options.track.label = options.kinds.join(' and ') + ' off';
19262        options.track.label = 'OFF';
19263      } // MenuItem is selectable
19264
19265
19266      options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
19267
19268      options.multiSelectable = false;
19269      return _TextTrackMenuItem.call(this, player, options) || this;
19270    }
19271    /**
19272     * Handle text track change
19273     *
19274     * @param {EventTarget~Event} event
19275     *        The event that caused this function to run
19276     */
19277
19278
19279    var _proto = OffTextTrackMenuItem.prototype;
19280
19281    _proto.handleTracksChange = function handleTracksChange(event) {
19282      var tracks = this.player().textTracks();
19283      var shouldBeSelected = true;
19284
19285      for (var i = 0, l = tracks.length; i < l; i++) {
19286        var track = tracks[i];
19287
19288        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
19289          shouldBeSelected = false;
19290          break;
19291        }
19292      } // Prevent redundant selected() calls because they may cause
19293      // screen readers to read the appended control text unnecessarily
19294
19295
19296      if (shouldBeSelected !== this.isSelected_) {
19297        this.selected(shouldBeSelected);
19298      }
19299    };
19300
19301    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
19302      var tracks = this.player().textTracks();
19303      var allHidden = true;
19304
19305      for (var i = 0, l = tracks.length; i < l; i++) {
19306        var track = tracks[i];
19307
19308        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
19309          allHidden = false;
19310          break;
19311        }
19312      }
19313
19314      if (allHidden) {
19315        this.player_.cache_.selectedLanguage = {
19316          enabled: false
19317        };
19318      }
19319    };
19320
19321    return OffTextTrackMenuItem;
19322  }(TextTrackMenuItem);
19323
19324  Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
19325
19326  /**
19327   * The base class for buttons that toggle specific text track types (e.g. subtitles)
19328   *
19329   * @extends MenuButton
19330   */
19331
19332  var TextTrackButton = /*#__PURE__*/function (_TrackButton) {
19333    inheritsLoose(TextTrackButton, _TrackButton);
19334
19335    /**
19336     * Creates an instance of this class.
19337     *
19338     * @param {Player} player
19339     *        The `Player` that this class should be attached to.
19340     *
19341     * @param {Object} [options={}]
19342     *        The key/value store of player options.
19343     */
19344    function TextTrackButton(player, options) {
19345      if (options === void 0) {
19346        options = {};
19347      }
19348
19349      options.tracks = player.textTracks();
19350      return _TrackButton.call(this, player, options) || this;
19351    }
19352    /**
19353     * Create a menu item for each text track
19354     *
19355     * @param {TextTrackMenuItem[]} [items=[]]
19356     *        Existing array of items to use during creation
19357     *
19358     * @return {TextTrackMenuItem[]}
19359     *         Array of menu items that were created
19360     */
19361
19362
19363    var _proto = TextTrackButton.prototype;
19364
19365    _proto.createItems = function createItems(items, TrackMenuItem) {
19366      if (items === void 0) {
19367        items = [];
19368      }
19369
19370      if (TrackMenuItem === void 0) {
19371        TrackMenuItem = TextTrackMenuItem;
19372      }
19373
19374      // Label is an override for the [track] off label
19375      // USed to localise captions/subtitles
19376      var label;
19377
19378      if (this.label_) {
19379        //label = this.label_ + " off";
19380        label = "OFF";
19381      } // Add an OFF menu item to turn all tracks off
19382
19383
19384      items.push(new OffTextTrackMenuItem(this.player_, {
19385        kinds: this.kinds_,
19386        kind: this.kind_,
19387        label: label
19388      }));
19389      this.hideThreshold_ += 1;
19390      var tracks = this.player_.textTracks();
19391
19392      if (!Array.isArray(this.kinds_)) {
19393        this.kinds_ = [this.kind_];
19394      }
19395
19396      for (var i = 0; i < tracks.length; i++) {
19397        var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
19398
19399        if (this.kinds_.indexOf(track.kind) > -1) {
19400          var item = new TrackMenuItem(this.player_, {
19401            track: track,
19402            kinds: this.kinds_,
19403            kind: this.kind_,
19404            // MenuItem is selectable
19405            selectable: true,
19406            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
19407            multiSelectable: false
19408          });
19409          item.addClass("vjs-" + track.kind + "-menu-item");
19410          items.push(item);
19411        }
19412      }
19413
19414      return items;
19415    };
19416
19417    return TextTrackButton;
19418  }(TrackButton);
19419
19420  Component$1.registerComponent('TextTrackButton', TextTrackButton);
19421
19422  /**
19423   * The chapter track menu item
19424   *
19425   * @extends MenuItem
19426   */
19427
19428  var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
19429    inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
19430
19431    /**
19432     * Creates an instance of this class.
19433     *
19434     * @param {Player} player
19435     *        The `Player` that this class should be attached to.
19436     *
19437     * @param {Object} [options]
19438     *        The key/value store of player options.
19439     */
19440    function ChaptersTrackMenuItem(player, options) {
19441      var _this;
19442
19443      var track = options.track;
19444      var cue = options.cue;
19445      var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
19446
19447      options.selectable = true;
19448      options.multiSelectable = false;
19449      options.label = cue.text;
19450      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
19451      _this = _MenuItem.call(this, player, options) || this;
19452      _this.track = track;
19453      _this.cue = cue;
19454      track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update));
19455      return _this;
19456    }
19457    /**
19458     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
19459     * {@link ClickableComponent} for more detailed information on what a click can be.
19460     *
19461     * @param {EventTarget~Event} [event]
19462     *        The `keydown`, `tap`, or `click` event that caused this function to be
19463     *        called.
19464     *
19465     * @listens tap
19466     * @listens click
19467     */
19468
19469
19470    var _proto = ChaptersTrackMenuItem.prototype;
19471
19472    _proto.handleClick = function handleClick(event) {
19473      _MenuItem.prototype.handleClick.call(this);
19474
19475      this.player_.currentTime(this.cue.startTime);
19476      this.update(this.cue.startTime);
19477    }
19478    /**
19479     * Update chapter menu item
19480     *
19481     * @param {EventTarget~Event} [event]
19482     *        The `cuechange` event that caused this function to run.
19483     *
19484     * @listens TextTrack#cuechange
19485     */
19486    ;
19487
19488    _proto.update = function update(event) {
19489      var cue = this.cue;
19490      var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
19491
19492      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
19493    };
19494
19495    return ChaptersTrackMenuItem;
19496  }(MenuItem);
19497
19498  Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
19499
19500  /**
19501   * The button component for toggling and selecting chapters
19502   * Chapters act much differently than other text tracks
19503   * Cues are navigation vs. other tracks of alternative languages
19504   *
19505   * @extends TextTrackButton
19506   */
19507
19508  var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) {
19509    inheritsLoose(ChaptersButton, _TextTrackButton);
19510
19511    /**
19512     * Creates an instance of this class.
19513     *
19514     * @param {Player} player
19515     *        The `Player` that this class should be attached to.
19516     *
19517     * @param {Object} [options]
19518     *        The key/value store of player options.
19519     *
19520     * @param {Component~ReadyCallback} [ready]
19521     *        The function to call when this function is ready.
19522     */
19523    function ChaptersButton(player, options, ready) {
19524      return _TextTrackButton.call(this, player, options, ready) || this;
19525    }
19526    /**
19527     * Builds the default DOM `className`.
19528     *
19529     * @return {string}
19530     *         The DOM `className` for this object.
19531     */
19532
19533
19534    var _proto = ChaptersButton.prototype;
19535
19536    _proto.buildCSSClass = function buildCSSClass() {
19537      return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19538    };
19539
19540    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19541      return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19542    }
19543    /**
19544     * Update the menu based on the current state of its items.
19545     *
19546     * @param {EventTarget~Event} [event]
19547     *        An event that triggered this function to run.
19548     *
19549     * @listens TextTrackList#addtrack
19550     * @listens TextTrackList#removetrack
19551     * @listens TextTrackList#change
19552     */
19553    ;
19554
19555    _proto.update = function update(event) {
19556      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 1,553 bytes, lines 19557-19610
19557        this.setTrack(this.findChaptersTrack());
19558      }
19559
19560      _TextTrackButton.prototype.update.call(this);
19561    }
19562    /**
19563     * Set the currently selected track for the chapters button.
19564     *
19565     * @param {TextTrack} track
19566     *        The new track to select. Nothing will change if this is the currently selected
19567     *        track.
19568     */
19569    ;
19570
19571    _proto.setTrack = function setTrack(track) {
19572      if (this.track_ === track) {
19573        return;
19574      }
19575
19576      if (!this.updateHandler_) {
19577        this.updateHandler_ = this.update.bind(this);
19578      } // here this.track_ refers to the old track instance
19579
19580
19581      if (this.track_) {
19582        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
19583
19584        if (remoteTextTrackEl) {
19585          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
19586        }
19587
19588        this.track_ = null;
19589      }
19590
19591      this.track_ = track; // here this.track_ refers to the new track instance
19592
19593      if (this.track_) {
19594        this.track_.mode = 'hidden';
19595
19596        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
19597
19598        if (_remoteTextTrackEl) {
19599          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
19600        }
19601      }
19602    }
19603    /**
19604     * Find the track object that is currently in use by this ChaptersButton
19605     *
19606     * @return {TextTrack|undefined}
19607     *         The current track or undefined if none was found.
19608     */
19609    ;
19610
19611    _proto.findChaptersTrack = function findChaptersTrack() {
19612      var tracks = this.player_.textTracks() || [];
19613
19614      for (var i = tracks.length - 1; i >= 0; i--) {
19615        // We will always choose the last track as our chaptersTrack
19616        var track = tracks[i];
19617
19618        if (track.kind === this.kind_) {
19619          return track;
19620        }
19621      }
19622    }
19623    /**
19624     * Get the caption for the ChaptersButton based on the track label. This will also
19625     * use the current tracks localized kind as a fallback if a label does not exist.
19626     *
19627     * @return {string}
19628     *         The tracks current label or the localized track kind.
19629     */
19630    ;
19631
19632    _proto.getMenuCaption = function getMenuCaption() {
19633      if (this.track_ && this.track_.label) {
19634        return this.track_.label;
19635      }
19636
19637      return this.localize(toTitleCase$1(this.kind_));
19638    }
19639    /**
19640     * Create menu from chapter track
19641     *
19642     * @return {Menu}
19643     *         New menu for the chapter buttons
19644     */
19645    ;
19646
19647    _proto.createMenu = function createMenu() {
19648      this.options_.title = this.getMenuCaption();
19649      return _TextTrackButton.prototype.createMenu.call(this);
19650    }
19651    /**
19652     * Create a menu item for each text track
19653     *
19654     * @return {TextTrackMenuItem[]}
19655     *         Array of menu items
19656     */
19657    ;
19658
19659    _proto.createItems = function createItems() {
19660      var items = [];
19661
19662      if (!this.track_) {
19663        return items;
19664      }
19665
19666      var cues = this.track_.cues;
19667
19668      if (!cues) {
19669        return items;
19670      }
19671
19672      for (var i = 0, l = cues.length; i < l; i++) {
19673        var cue = cues[i];
19674        var mi = new ChaptersTrackMenuItem(this.player_, {
19675          track: this.track_,
19676          cue: cue
19677        });
19678        items.push(mi);
19679      }
19680
19681      return items;
19682    };
19683
19684    return ChaptersButton;
19685  }(TextTrackButton);
19686  /**
19687   * `kind` of TextTrack to look for to associate it with this menu.
19688   *
19689   * @type {string}
19690   * @private
19691   */
19692
19693
19694  ChaptersButton.prototype.kind_ = 'chapters';
19695  /**
19696   * The text that should display over the `ChaptersButton`s controls. Added for localization.
19697   *
19698   * @type {string}
19699   * @private
19700   */
19701
19702  ChaptersButton.prototype.controlText_ = 'Chapters';
19703  Component$1.registerComponent('ChaptersButton', ChaptersButton);
19704
19705  /**
19706   * The button component for toggling and selecting descriptions
19707   *
19708   * @extends TextTrackButton
19709   */
19710
19711  var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) {
19712    inheritsLoose(DescriptionsButton, _TextTrackButton);
19713
19714    /**
19715     * Creates an instance of this class.
19716     *
19717     * @param {Player} player
19718     *        The `Player` that this class should be attached to.
19719     *
19720     * @param {Object} [options]
19721     *        The key/value store of player options.
19722     *
19723     * @param {Component~ReadyCallback} [ready]
19724     *        The function to call when this component is ready.
19725     */
19726    function DescriptionsButton(player, options, ready) {
19727      var _this;
19728
19729      _this = _TextTrackButton.call(this, player, options, ready) || this;
19730      var tracks = player.textTracks();
19731      var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange);
19732      tracks.addEventListener('change', changeHandler);
19733
19734      _this.on('dispose', function () {
19735        tracks.removeEventListener('change', changeHandler);
19736      });
19737
19738      return _this;
19739    }
19740    /**
19741     * Handle text track change
19742     *
19743     * @param {EventTarget~Event} event
19744     *        The event that caused this function to run
19745     *
19746     * @listens TextTrackList#change
19747     */
19748
19749
19750    var _proto = DescriptionsButton.prototype;
19751
19752    _proto.handleTracksChange = function handleTracksChange(event) {
19753      var tracks = this.player().textTracks();
19754      var disabled = false; // Check whether a track of a different kind is showing
19755
19756      for (var i = 0, l = tracks.length; i < l; i++) {
19757        var track = tracks[i];
19758
19759        if (track.kind !== this.kind_ && track.mode === 'showing') {
19760          disabled = true;
19761          break;
19762        }
19763      } // If another track is showing, disable this menu button
19764
19765
19766      if (disabled) {
19767        this.disable();
19768      } else {
19769        this.enable();
19770      }
19771    }
19772    /**
19773     * Builds the default DOM `className`.
19774     *
19775     * @return {string}
19776     *         The DOM `className` for this object.
19777     */
19778    ;
19779
19780    _proto.buildCSSClass = function buildCSSClass() {
19781      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19782    };
19783
19784    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19785      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19786    };
19787
19788    return DescriptionsButton;
19789  }(TextTrackButton);
19790  /**
19791   * `kind` of TextTrack to look for to associate it with this menu.
19792   *
19793   * @type {string}
19794   * @private
19795   */
19796
19797
19798  DescriptionsButton.prototype.kind_ = 'descriptions';
19799  /**
19800   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
19801   *
19802   * @type {string}
19803   * @private
19804   */
19805
19806  DescriptionsButton.prototype.controlText_ = 'Descriptions';
19807  Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
19808
19809  /**
19810   * The button component for toggling and selecting subtitles
19811   *
19812   * @extends TextTrackButton
19813   */
19814
19815  var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) {
19816    inheritsLoose(SubtitlesButton, _TextTrackButton);
19817
19818    /**
19819     * Creates an instance of this class.
19820     *
19821     * @param {Player} player
19822     *        The `Player` that this class should be attached to.
19823     *
19824     * @param {Object} [options]
19825     *        The key/value store of player options.
19826     *
19827     * @param {Component~ReadyCallback} [ready]
19828     *        The function to call when this component is ready.
19829     */
19830    function SubtitlesButton(player, options, ready) {
19831      return _TextTrackButton.call(this, player, options, ready) || this;
19832    }
19833    /**
19834     * Builds the default DOM `className`.
19835     *
19836     * @return {string}
19837     *         The DOM `className` for this object.
19838     */
19839
19840
19841    var _proto = SubtitlesButton.prototype;
19842
19843    _proto.buildCSSClass = function buildCSSClass() {
19844      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19845    };
19846
19847    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19848      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19849    };
19850
19851    return SubtitlesButton;
19852  }(TextTrackButton);
19853  /**
19854   * `kind` of TextTrack to look for to associate it with this menu.
19855   *
19856   * @type {string}
19857   * @private
19858   */
19859
19860
19861  SubtitlesButton.prototype.kind_ = 'subtitles';
19862  /**
19863   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
19864   *
19865   * @type {string}
19866   * @private
19867   */
19868
19869  SubtitlesButton.prototype.controlText_ = 'Subtitles';
19870  Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
19871
19872  /**
19873   * The menu item for caption track settings menu
19874   *
19875   * @extends TextTrackMenuItem
19876   */
19877
19878  var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
19879    inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
19880
19881    /**
19882     * Creates an instance of this class.
19883     *
19884     * @param {Player} player
19885     *        The `Player` that this class should be attached to.
19886     *
19887     * @param {Object} [options]
19888     *        The key/value store of player options.
19889     */
19890    function CaptionSettingsMenuItem(player, options) {
19891      var _this;
19892
19893      options.track = {
19894        player: player,
19895        kind: options.kind,
19896        label: options.kind + ' settings',
19897        selectable: false,
19898        "default": false,
19899        mode: 'disabled'
19900      }; // CaptionSettingsMenuItem has no concept of 'selected'
19901
19902      options.selectable = false;
19903      options.name = 'CaptionSettingsMenuItem';
19904      _this = _TextTrackMenuItem.call(this, player, options) || this;
19905
19906      _this.addClass('vjs-texttrack-settings');
19907
19908      _this.controlText(', opens ' + options.kind + ' settings dialog');
19909
19910      return _this;
19911    }
19912    /**
19913     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
19914     * {@link ClickableComponent} for more detailed information on what a click can be.
19915     *
19916     * @param {EventTarget~Event} [event]
19917     *        The `keydown`, `tap`, or `click` event that caused this function to be
19918     *        called.
19919     *
19920     * @listens tap
19921     * @listens click
19922     */
19923
19924
19925    var _proto = CaptionSettingsMenuItem.prototype;
19926
19927    _proto.handleClick = function handleClick(event) {
19928      this.player().getChild('textTrackSettings').open();
19929    };
19930
19931    return CaptionSettingsMenuItem;
19932  }(TextTrackMenuItem);
19933
19934  Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
19935
19936  /**
19937   * The button component for toggling and selecting captions
19938   *
19939   * @extends TextTrackButton
19940   */
19941
19942  var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) {
19943    inheritsLoose(CaptionsButton, _TextTrackButton);
19944
19945    /**
19946     * Creates an instance of this class.
19947     *
19948     * @param {Player} player
19949     *        The `Player` that this class should be attached to.
19950     *
19951     * @param {Object} [options]
19952     *        The key/value store of player options.
19953     *
19954     * @param {Component~ReadyCallback} [ready]
19955     *        The function to call when this component is ready.
19956     */
19957    function CaptionsButton(player, options, ready) {
19958      return _TextTrackButton.call(this, player, options, ready) || this;
19959    }
19960    /**
19961     * Builds the default DOM `className`.
19962     *
19963     * @return {string}
19964     *         The DOM `className` for this object.
19965     */
19966
19967
19968    var _proto = CaptionsButton.prototype;
19969
19970    _proto.buildCSSClass = function buildCSSClass() {
19971      return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19972    };
19973
19974    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19975      return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19976    }
19977    /**
19978     * Create caption menu items
19979     *
19980     * @return {CaptionSettingsMenuItem[]}
19981     *         The array of current menu items.
19982     */
19983    ;
19984
19985    _proto.createItems = function createItems() {
19986      var items = [];
19987
19988      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
19989        items.push(new CaptionSettingsMenuItem(this.player_, {
19990          kind: this.kind_
19991        }));
19992        this.hideThreshold_ += 1;
19993      }
19994
19995      return _TextTrackButton.prototype.createItems.call(this, items);
19996    };
19997
19998    return CaptionsButton;
19999  }(TextTrackButton);
20000  /**
20001   * `kind` of TextTrack to look for to associate it with this menu.
20002   *
20003   * @type {string}
20004   * @private
20005   */
20006
20007
20008  CaptionsButton.prototype.kind_ = 'captions';
20009  /**
20010   * The text that should display over the `CaptionsButton`s controls. Added for localization.
20011   *
20012   * @type {string}
20013   * @private
20014   */
20015
20016  CaptionsButton.prototype.controlText_ = 'Captions';
20017  Component$1.registerComponent('CaptionsButton', CaptionsButton);
20018
20019  /**
20020   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
20021   * in the SubsCapsMenu.
20022   *
20023   * @extends TextTrackMenuItem
20024   */
20025
20026  var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
20027    inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
20028
20029    function SubsCapsMenuItem() {
20030      return _TextTrackMenuItem.apply(this, arguments) || this;
20031    }
20032
20033    var _proto = SubsCapsMenuItem.prototype;
20034
20035    _proto.createEl = function createEl$1(type, props, attrs) {
20036      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, props, attrs);
20037
20038      var parentSpan = el.querySelector('.vjs-menu-item-text');
20039
20040      if (this.options_.track.kind === 'captions') {
20041        parentSpan.appendChild(createEl('span', {
20042          className: 'vjs-icon-placeholder'
20043        }, {
20044          'aria-hidden': true
20045        }));
20046        parentSpan.appendChild(createEl('span', {
20047          className: 'vjs-control-text',
20048          // space added as the text will visually flow with the
20049          // label
20050          textContent: " " + this.localize('Captions')
20051        }));
20052      }
20053
20054      return el;
20055    };
20056
20057    return SubsCapsMenuItem;
20058  }(TextTrackMenuItem);
20059
20060  Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
20061
20062  /**
20063   * The button component for toggling and selecting captions and/or subtitles
20064   *
20065   * @extends TextTrackButton
20066   */
20067
20068  var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) {
20069    inheritsLoose(SubsCapsButton, _TextTrackButton);
20070
20071    function SubsCapsButton(player, options) {
20072      var _this;
20073
20074      if (options === void 0) {
20075        options = {};
20076      }
20077
20078      _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
20079      // "captions and subtitles" other locales use "subtitles"
20080
20081      _this.label_ = 'subtitles';
20082
20083      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
20084        _this.label_ = 'captions';
20085      }
20086
20087      _this.menuButton_.controlText(toTitleCase$1(_this.label_));
20088
20089      return _this;
20090    }
20091    /**
20092     * Builds the default DOM `className`.
20093     *
20094     * @return {string}
20095     *         The DOM `className` for this object.
20096     */
20097
20098
20099    var _proto = SubsCapsButton.prototype;
20100
20101    _proto.buildCSSClass = function buildCSSClass() {
20102      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
20103    };
20104
20105    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
20106      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
20107    }
20108    /**
20109     * Create caption/subtitles menu items
20110     *
20111     * @return {CaptionSettingsMenuItem[]}
20112     *         The array of current menu items.
20113     */
20114    ;
20115
20116    _proto.createItems = function createItems() {
20117      var items = [];
20118
20119      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
20120        items.push(new CaptionSettingsMenuItem(this.player_, {
20121          kind: this.label_
20122        }));
20123        this.hideThreshold_ += 1;
20124      }
20125
20126      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
20127      return items;
20128    };
20129
20130    return SubsCapsButton;
20131  }(TextTrackButton);
20132  /**
20133   * `kind`s of TextTrack to look for to associate it with this menu.
20134   *
20135   * @type {array}
20136   * @private
20137   */
20138
20139
20140  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
20141  /**
20142   * The text that should display over the `SubsCapsButton`s controls.
20143   *
20144   *
20145   * @type {string}
20146   * @private
20147   */
20148
20149  SubsCapsButton.prototype.controlText_ = 'Subtitles';
20150  Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
20151
20152  /**
20153   * An {@link AudioTrack} {@link MenuItem}
20154   *
20155   * @extends MenuItem
20156   */
20157
20158  var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
20159    inheritsLoose(AudioTrackMenuItem, _MenuItem);
20160
20161    /**
20162     * Creates an instance of this class.
20163     *
20164     * @param {Player} player
20165     *        The `Player` that this class should be attached to.
20166     *
20167     * @param {Object} [options]
20168     *        The key/value store of player options.
20169     */
20170    function AudioTrackMenuItem(player, options) {
20171      var _this;
20172
20173      var track = options.track;
20174      var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
20175
20176      options.label = track.label || track.language || 'Unknown';
20177      options.selected = track.enabled;
20178      _this = _MenuItem.call(this, player, options) || this;
20179      _this.track = track;
20180
20181      _this.addClass("vjs-" + track.kind + "-menu-item");
20182
20183      var changeHandler = function changeHandler() {
20184        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
20185          args[_key] = arguments[_key];
20186        }
20187
20188        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
20189      };
20190
20191      tracks.addEventListener('change', changeHandler);
20192
20193      _this.on('dispose', function () {
20194        tracks.removeEventListener('change', changeHandler);
20195      });
20196
20197      return _this;
20198    }
20199
20200    var _proto = AudioTrackMenuItem.prototype;
20201
20202    _proto.createEl = function createEl(type, props, attrs) {
20203      var el = _MenuItem.prototype.createEl.call(this, type, props, attrs);
20204
20205      var parentSpan = el.querySelector('.vjs-menu-item-text');
20206
20207      if (this.options_.track.kind === 'main-desc') {
20208        parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', {
20209          className: 'vjs-icon-placeholder'
20210        }, {
20211          'aria-hidden': true
20212        }));
20213        parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', {
20214          className: 'vjs-control-text',
20215          textContent: this.localize('Descriptions')
20216        }));
20217      }
20218
20219      return el;
20220    }
20221    /**
20222     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
20223     * for more detailed information on what a click can be.
20224     *
20225     * @param {EventTarget~Event} [event]
20226     *        The `keydown`, `tap`, or `click` event that caused this function to be
20227     *        called.
20228     *
20229     * @listens tap
20230     * @listens click
20231     */
20232    ;
20233
20234    _proto.handleClick = function handleClick(event) {
20235      _MenuItem.prototype.handleClick.call(this, event); // the audio track list will automatically toggle other tracks
20236      // off for us.
20237
20238
20239      this.track.enabled = true;
20240    }
20241    /**
20242     * Handle any {@link AudioTrack} change.
20243     *
20244     * @param {EventTarget~Event} [event]
20245     *        The {@link AudioTrackList#change} event that caused this to run.
20246     *
20247     * @listens AudioTrackList#change
20248     */
20249    ;
20250
20251    _proto.handleTracksChange = function handleTracksChange(event) {
20252      this.selected(this.track.enabled);
20253    };
20254
20255    return AudioTrackMenuItem;
20256  }(MenuItem);
20257
20258  Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
20259
20260  /**
20261   * The base class for buttons that toggle specific {@link AudioTrack} types.
20262   *
20263   * @extends TrackButton
20264   */
20265
20266  var AudioTrackButton = /*#__PURE__*/function (_TrackButton) {
20267    inheritsLoose(AudioTrackButton, _TrackButton);
20268
20269    /**
20270     * Creates an instance of this class.
20271     *
20272     * @param {Player} player
20273     *        The `Player` that this class should be attached to.
20274     *
20275     * @param {Object} [options={}]
20276     *        The key/value store of player options.
20277     */
20278    function AudioTrackButton(player, options) {
20279      if (options === void 0) {
20280        options = {};
20281      }
20282
20283      options.tracks = player.audioTracks();
20284      return _TrackButton.call(this, player, options) || this;
20285    }
20286    /**
20287     * Builds the default DOM `className`.
20288     *
20289     * @return {string}
20290     *         The DOM `className` for this object.
20291     */
20292
20293
20294    var _proto = AudioTrackButton.prototype;
20295
20296    _proto.buildCSSClass = function buildCSSClass() {
20297      return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
20298    };
20299
20300    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
20301      return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
20302    }
20303    /**
20304     * Create a menu item for each audio track
20305     *
20306     * @param {AudioTrackMenuItem[]} [items=[]]
20307     *        An array of existing menu items to use.
20308     *
20309     * @return {AudioTrackMenuItem[]}
20310     *         An array of menu items
20311     */
20312    ;
20313
20314    _proto.createItems = function createItems(items) {
20315      if (items === void 0) {
20316        items = [];
20317      }
20318
20319      // if there's only one audio track, there no point in showing it
20320      this.hideThreshold_ = 1;
20321      var tracks = this.player_.audioTracks();
20322
20323      for (var i = 0; i < tracks.length; i++) {
20324        var track = tracks[i];
20325        items.push(new AudioTrackMenuItem(this.player_, {
20326          track: track,
20327          // MenuItem is selectable
20328          selectable: true,
20329          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
20330          multiSelectable: false
20331        }));
20332      }
20333
20334      return items;
20335    };
20336
20337    return AudioTrackButton;
20338  }(TrackButton);
20339  /**
20340   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
20341   *
20342   * @type {string}
20343   * @private
20344   */
20345
20346
20347  AudioTrackButton.prototype.controlText_ = 'Audio Track';
20348  Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
20349
20350  /**
20351   * The specific menu item type for selecting a playback rate.
20352   *
20353   * @extends MenuItem
20354   */
20355
20356  var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) {
20357    inheritsLoose(PlaybackRateMenuItem, _MenuItem);
20358
20359    /**
20360     * Creates an instance of this class.
20361     *
20362     * @param {Player} player
20363     *        The `Player` that this class should be attached to.
20364     *
20365     * @param {Object} [options]
20366     *        The key/value store of player options.
20367     */
20368    function PlaybackRateMenuItem(player, options) {
20369      var _this;
20370
20371      var label = options.rate;
20372      var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
20373
20374      options.label = label;
20375      options.selected = rate === player.playbackRate();
20376      options.selectable = true;
20377      options.multiSelectable = false;
20378      _this = _MenuItem.call(this, player, options) || this;
20379      _this.label = label;
20380      _this.rate = rate;
20381
20382      _this.on(player, 'ratechange', function (e) {
20383        return _this.update(e);
20384      });
20385
20386      return _this;
20387    }
20388    /**
20389     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
20390     * {@link ClickableComponent} for more detailed information on what a click can be.
20391     *
20392     * @param {EventTarget~Event} [event]
20393     *        The `keydown`, `tap`, or `click` event that caused this function to be
20394     *        called.
20395     *
20396     * @listens tap
20397     * @listens click
20398     */
20399
20400
20401    var _proto = PlaybackRateMenuItem.prototype;
20402
20403    _proto.handleClick = function handleClick(event) {
20404      _MenuItem.prototype.handleClick.call(this);
20405
20406      this.player().playbackRate(this.rate);
20407    }
20408    /**
20409     * Update the PlaybackRateMenuItem when the playbackrate changes.
20410     *
20411     * @param {EventTarget~Event} [event]
20412     *        The `ratechange` event that caused this function to run.
20413     *
20414     * @listens Player#ratechange
20415     */
20416    ;
20417
20418    _proto.update = function update(event) {
20419      this.selected(this.player().playbackRate() === this.rate);
20420    };
20421
20422    return PlaybackRateMenuItem;
20423  }(MenuItem);
20424  /**
20425   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
20426   *
20427   * @type {string}
20428   * @private
20429   */
20430
20431
20432  PlaybackRateMenuItem.prototype.contentElType = 'button';
20433  Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
20434
20435  /**
20436   * The component for controlling the playback rate.
20437   *
20438   * @extends MenuButton
20439   */
20440
20441  var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) {
20442    inheritsLoose(PlaybackRateMenuButton, _MenuButton);
20443
20444    /**
20445     * Creates an instance of this class.
20446     *
20447     * @param {Player} player
20448     *        The `Player` that this class should be attached to.
20449     *
20450     * @param {Object} [options]
20451     *        The key/value store of player options.
20452     */
20453    function PlaybackRateMenuButton(player, options) {
20454      var _this;
20455
20456      _this = _MenuButton.call(this, player, options) || this;
20457
20458      _this.menuButton_.el_.setAttribute('aria-describedby', _this.labelElId_);
20459
20460      _this.updateVisibility();
20461
20462      _this.updateLabel();
20463
20464      _this.on(player, 'loadstart', function (e) {
20465        return _this.updateVisibility(e);
20466      });
20467
20468      _this.on(player, 'ratechange', function (e) {
20469        return _this.updateLabel(e);
20470      });
20471
20472      _this.on(player, 'playbackrateschange', function (e) {
20473        return _this.handlePlaybackRateschange(e);
20474      });
20475
20476      return _this;
20477    }
20478    /**
20479     * Create the `Component`'s DOM element
20480     *
20481     * @return {Element}
20482     *         The element that was created.
20483     */
20484
20485
20486    var _proto = PlaybackRateMenuButton.prototype;
20487
20488    _proto.createEl = function createEl$1() {
20489      var el = _MenuButton.prototype.createEl.call(this);
20490
20491      this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
20492      this.labelEl_ = createEl('div', {
20493        className: 'vjs-playback-rate-value',
20494        id: this.labelElId_,
20495        textContent: '1x'
20496      });
20497      el.appendChild(this.labelEl_);
20498      return el;
20499    };
20500
20501    _proto.dispose = function dispose() {
20502      this.labelEl_ = null;
20503
20504      _MenuButton.prototype.dispose.call(this);
20505    }
20506    /**
20507     * Builds the default DOM `className`.
20508     *
20509     * @return {string}
20510     *         The DOM `className` for this object.
20511     */
20512    ;
20513
20514    _proto.buildCSSClass = function buildCSSClass() {
20515      return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
20516    };
20517
20518    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
20519      return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
20520    }
20521    /**
20522     * Create the list of menu items. Specific to each subclass.
20523     *
20524     */
20525    ;
20526
20527    _proto.createItems = function createItems() {
20528      var rates = this.playbackRates();
20529      var items = [];
20530
20531      for (var i = rates.length - 1; i >= 0; i--) {
20532        items.push(new PlaybackRateMenuItem(this.player(), {
20533          rate: rates[i] + 'x'
20534        }));
20535      }
20536
20537      return items;
20538    }
20539    /**
20540     * Updates ARIA accessibility attributes
20541     */
20542    ;
20543
20544    _proto.updateARIAAttributes = function updateARIAAttributes() {
20545      // Current playback rate
20546      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
20547    }
20548    /**
20549     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
20550     * {@link ClickableComponent} for more detailed information on what a click can be.
20551     *
20552     * @param {EventTarget~Event} [event]
20553     *        The `keydown`, `tap`, or `click` event that caused this function to be
20554     *        called.
20555     *
20556     * @listens tap
20557     * @listens click
20558     */
20559    ;
20560
20561    _proto.handleClick = function handleClick(event) {
20562      // select next rate option
20563      var currentRate = this.player().playbackRate();
20564      var rates = this.playbackRates(); // this will select first one if the last one currently selected
20565
20566      var newRate = rates[0];
20567
20568      for (var i = 0; i < rates.length; i++) {
20569        if (rates[i] > currentRate) {
20570          newRate = rates[i];
20571          break;
20572        }
20573      }
20574
20575      this.player().playbackRate(newRate);
20576    }
20577    /**
20578     * On playbackrateschange, update the menu to account for the new items.
20579     *
20580     * @listens Player#playbackrateschange
20581     */
20582    ;
20583
20584    _proto.handlePlaybackRateschange = function handlePlaybackRateschange(event) {
20585      this.update();
20586    }
20587    /**
20588     * Get possible playback rates
20589     *
20590     * @return {Array}
20591     *         All possible playback rates
20592     */
20593    ;
20594
20595    _proto.playbackRates = function playbackRates() {
20596      var player = this.player();
20597      return player.playbackRates && player.playbackRates() || [];
20598    }
20599    /**
20600     * Get whether playback rates is supported by the tech
20601     * and an array of playback rates exists
20602     *
20603     * @return {boolean}
20604     *         Whether changing playback rate is supported
20605     */
20606    ;
20607
20608    _proto.playbackRateSupported = function playbackRateSupported() {
20609      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
20610    }
20611    /**
20612     * Hide playback rate controls when they're no playback rate options to select
20613     *
20614     * @param {EventTarget~Event} [event]
20615     *        The event that caused this function to run.
20616     *
20617     * @listens Player#loadstart
20618     */
20619    ;
20620
20621    _proto.updateVisibility = function updateVisibility(event) {
20622      if (this.playbackRateSupported()) {
20623        this.removeClass('vjs-hidden');
20624      } else {
20625        this.addClass('vjs-hidden');
20626      }
20627    }
20628    /**
20629     * Update button label when rate changed
20630     *
20631     * @param {EventTarget~Event} [event]
20632     *        The event that caused this function to run.
20633     *
20634     * @listens Player#ratechange
20635     */
20636    ;
20637
20638    _proto.updateLabel = function updateLabel(event) {
20639      if (this.playbackRateSupported()) {
20640        this.labelEl_.textContent = this.player().playbackRate() + 'x';
20641      }
20642    };
20643
20644    return PlaybackRateMenuButton;
20645  }(MenuButton);
20646  /**
20647   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
20648   *
20649   * @type {string}
20650   * @private
20651   */
20652
20653
20654  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
20655  Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
20656
20657  /**
20658   * Just an empty spacer element that can be used as an append point for plugins, etc.
20659   * Also can be used to create space between elements when necessary.
20660   *
20661   * @extends Component
20662   */
20663
20664  var Spacer = /*#__PURE__*/function (_Component) {
20665    inheritsLoose(Spacer, _Component);
20666
20667    function Spacer() {
20668      return _Component.apply(this, arguments) || this;
20669    }
20670
20671    var _proto = Spacer.prototype;
20672
20673    /**
20674    * Builds the default DOM `className`.
20675    *
20676    * @return {string}
20677    *         The DOM `className` for this object.
20678    */
20679    _proto.buildCSSClass = function buildCSSClass() {
20680      return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
20681    }
20682    /**
20683     * Create the `Component`'s DOM element
20684     *
20685     * @return {Element}
20686     *         The element that was created.
20687     */
20688    ;
20689
20690    _proto.createEl = function createEl(tag, props, attributes) {
20691      if (tag === void 0) {
20692        tag = 'div';
20693      }
20694
20695      if (props === void 0) {
20696        props = {};
20697      }
20698
20699      if (attributes === void 0) {
20700        attributes = {};
20701      }
20702
20703      if (!props.className) {
20704        props.className = this.buildCSSClass();
20705      }
20706
20707      return _Component.prototype.createEl.call(this, tag, props, attributes);
20708    };
20709
20710    return Spacer;
20711  }(Component$1);
20712
20713  Component$1.registerComponent('Spacer', Spacer);
20714
20715  /**
20716   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
20717   *
20718   * @extends Spacer
20719   */
20720
20721  var CustomControlSpacer = /*#__PURE__*/function (_Spacer) {
20722    inheritsLoose(CustomControlSpacer, _Spacer);
20723
20724    function CustomControlSpacer() {
20725      return _Spacer.apply(this, arguments) || this;
20726    }
20727
20728    var _proto = CustomControlSpacer.prototype;
20729
20730    /**
20731     * Builds the default DOM `className`.
20732     *
20733     * @return {string}
20734     *         The DOM `className` for this object.
20735     */
20736    _proto.buildCSSClass = function buildCSSClass() {
20737      return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
20738    }
20739    /**
20740     * Create the `Component`'s DOM element
20741     *
20742     * @return {Element}
20743     *         The element that was created.
20744     */
20745    ;
20746
20747    _proto.createEl = function createEl() {
20748      return _Spacer.prototype.createEl.call(this, 'div', {
20749        className: this.buildCSSClass(),
20750        // No-flex/table-cell mode requires there be some content
20751        // in the cell to fill the remaining space of the table.
20752        textContent: "\xA0"
20753      });
20754    };
20755
20756    return CustomControlSpacer;
20757  }(Spacer);
20758
20759  Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
20760
20761  /**
20762   * Container of main controls.
20763   *
20764   * @extends Component
20765   */
20766
20767  var ControlBar = /*#__PURE__*/function (_Component) {
20768    inheritsLoose(ControlBar, _Component);
20769
20770    function ControlBar() {
20771      return _Component.apply(this, arguments) || this;
20772    }
20773
20774    var _proto = ControlBar.prototype;
20775
20776    /**
20777     * Create the `Component`'s DOM element
20778     *
20779     * @return {Element}
20780     *         The element that was created.
20781     */
20782    _proto.createEl = function createEl() {
20783      return _Component.prototype.createEl.call(this, 'div', {
20784        className: 'vjs-control-bar',
20785        dir: 'ltr'
20786      });
20787    };
20788
20789    return ControlBar;
20790  }(Component$1);
20791  /**
20792   * Default options for `ControlBar`
20793   *
20794   * @type {Object}
20795   * @private
20796   */
20797
20798
20799  ControlBar.prototype.options_ = {
20800    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
20801  };
20802
20803  if ('exitPictureInPicture' in document_1) {
20804    ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle');
20805  }
20806
20807  Component$1.registerComponent('ControlBar', ControlBar);
20808
20809  /**
20810   * A display that indicates an error has occurred. This means that the video
20811   * is unplayable.
20812   *
20813   * @extends ModalDialog
20814   */
20815
20816  var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) {
20817    inheritsLoose(ErrorDisplay, _ModalDialog);
20818
20819    /**
20820     * Creates an instance of this class.
20821     *
20822     * @param  {Player} player
20823     *         The `Player` that this class should be attached to.
20824     *
20825     * @param  {Object} [options]
20826     *         The key/value store of player options.
20827     */
20828    function ErrorDisplay(player, options) {
20829      var _this;
20830
20831      _this = _ModalDialog.call(this, player, options) || this;
20832
20833      _this.on(player, 'error', function (e) {
20834        return _this.open(e);
20835      });
20836
20837      return _this;
20838    }
20839    /**
20840     * Builds the default DOM `className`.
20841     *
20842     * @return {string}
20843     *         The DOM `className` for this object.
20844     *
20845     * @deprecated Since version 5.
20846     */
20847
20848
20849    var _proto = ErrorDisplay.prototype;
20850
20851    _proto.buildCSSClass = function buildCSSClass() {
20852      return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
20853    }
20854    /**
20855     * Gets the localized error message based on the `Player`s error.
20856     *
20857     * @return {string}
20858     *         The `Player`s error message localized or an empty string.
20859     */
20860    ;
20861
20862    _proto.content = function content() {
20863      var error = this.player().error();
20864      return error ? this.localize(error.message) : '';
20865    };
20866
20867    return ErrorDisplay;
20868  }(ModalDialog);
20869  /**
20870   * The default options for an `ErrorDisplay`.
20871   *
20872   * @private
20873   */
20874
20875
20876  ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
20877    pauseOnOpen: false,
20878    fillAlways: true,
20879    temporary: false,
20880    uncloseable: true
20881  });
20882  Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
20883
20884  var LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
20885  var COLOR_BLACK = ['#000', 'Black'];
20886  var COLOR_BLUE = ['#00F', 'Blue'];
20887  var COLOR_CYAN = ['#0FF', 'Cyan'];
20888  var COLOR_GREEN = ['#0F0', 'Green'];
20889  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
20890  var COLOR_RED = ['#F00', 'Red'];
20891  var COLOR_WHITE = ['#FFF', 'White'];
20892  var COLOR_YELLOW = ['#FF0', 'Yellow'];
20893  var OPACITY_OPAQUE = ['1', 'Opaque'];
20894  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
20895  var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
20896  //
20897  // Possible keys include:
20898  //
20899  // `default`:
20900  //   The default option index. Only needs to be provided if not zero.
20901  // `parser`:
20902  //   A function which is used to parse the value from the selected option in
20903  //   a customized way.
20904  // `selector`:
20905  //   The selector used to find the associated <select> element.
20906
20907  var selectConfigs = {
20908    backgroundColor: {
20909      selector: '.vjs-bg-color > select',
20910      id: 'captions-background-color-%s',
20911      label: 'Color',
20912      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
20913    },
20914    backgroundOpacity: {
20915      selector: '.vjs-bg-opacity > select',
20916      id: 'captions-background-opacity-%s',
20917      label: 'Transparency',
20918      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
20919    },
20920    color: {
20921      selector: '.vjs-fg-color > select',
20922      id: 'captions-foreground-color-%s',
20923      label: 'Color',
20924      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
20925    },
20926    edgeStyle: {
20927      selector: '.vjs-edge-style > select',
20928      id: '%s',
20929      label: 'Text Edge Style',
20930      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
20931    },
20932    fontFamily: {
20933      selector: '.vjs-font-family > select',
20934      id: 'captions-font-family-%s',
20935      label: 'Font Family',
20936      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
20937    },
20938    fontPercent: {
20939      selector: '.vjs-font-percent > select',
20940      id: 'captions-font-size-%s',
20941      label: 'Font Size',
20942      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%']],
20943      "default": 2,
20944      parser: function parser(v) {
20945        return v === '1.00' ? null : Number(v);
20946      }
20947    },
20948    textOpacity: {
20949      selector: '.vjs-text-opacity > select',
20950      id: 'captions-foreground-opacity-%s',
20951      label: 'Transparency',
20952      options: [OPACITY_OPAQUE, OPACITY_SEMI]
20953    },
20954    // Options for this object are defined below.
20955    windowColor: {
20956      selector: '.vjs-window-color > select',
20957      id: 'captions-window-color-%s',
20958      label: 'Color'
20959    },
20960    // Options for this object are defined below.
20961    windowOpacity: {
20962      selector: '.vjs-window-opacity > select',
20963      id: 'captions-window-opacity-%s',
20964      label: 'Transparency',
20965      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
20966    }
20967  };
20968  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
20969  /**
20970   * Get the actual value of an option.
20971   *
20972   * @param  {string} value
20973   *         The value to get
20974   *
20975   * @param  {Function} [parser]
20976   *         Optional function to adjust the value.
20977   *
20978   * @return {Mixed}
20979   *         - Will be `undefined` if no value exists
20980   *         - Will be `undefined` if the given value is "none".
20981   *         - Will be the actual value otherwise.
20982   *
20983   * @private
20984   */
20985
20986  function parseOptionValue(value, parser) {
20987    if (parser) {
20988      value = parser(value);
20989    }
20990
20991    if (value && value !== 'none') {
20992      return value;
20993    }
20994  }
20995  /**
20996   * Gets the value of the selected <option> element within a <select> element.
20997   *
20998   * @param  {Element} el
20999   *         the element to look in
21000   *
21001   * @param  {Function} [parser]
21002   *         Optional function to adjust the value.
21003   *
21004   * @return {Mixed}
21005   *         - Will be `undefined` if no value exists
21006   *         - Will be `undefined` if the given value is "none".
21007   *         - Will be the actual value otherwise.
21008   *
21009   * @private
21010   */
21011
21012
21013  function getSelectedOptionValue(el, parser) {
21014    var value = el.options[el.options.selectedIndex].value;
21015    return parseOptionValue(value, parser);
21016  }
21017  /**
21018   * Sets the selected <option> element within a <select> element based on a
21019   * given value.
21020   *
21021   * @param {Element} el
21022   *        The element to look in.
21023   *
21024   * @param {string} value
21025   *        the property to look on.
21026   *
21027   * @param {Function} [parser]
21028   *        Optional function to adjust the value before comparing.
21029   *
21030   * @private
21031   */
21032
21033
21034  function setSelectedOption(el, value, parser) {
21035    if (!value) {
21036      return;
21037    }
21038
21039    for (var i = 0; i < el.options.length; i++) {
21040      if (parseOptionValue(el.options[i].value, parser) === value) {
21041        el.selectedIndex = i;
21042        break;
21043      }
21044    }
21045  }
21046  /**
21047   * Manipulate Text Tracks settings.
21048   *
21049   * @extends ModalDialog
21050   */
21051
21052
21053  var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) {
21054    inheritsLoose(TextTrackSettings, _ModalDialog);
21055
21056    /**
21057     * Creates an instance of this class.
21058     *
21059     * @param {Player} player
21060     *         The `Player` that this class should be attached to.
21061     *
21062     * @param {Object} [options]
21063     *         The key/value store of player options.
21064     */
21065    function TextTrackSettings(player, options) {
21066      var _this;
21067
21068      options.temporary = false;
21069      _this = _ModalDialog.call(this, player, options) || this;
21070      _this.updateDisplay = _this.updateDisplay.bind(assertThisInitialized(_this));
21070 // fill the modal and pretend we have opened it
21071
21072      _this.fill();
21073
21074      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
21075      _this.endDialog = createEl('p', {
21076        className: 'vjs-control-text',
21077        textContent: _this.localize('End of dialog window.')
21078      });
21079
21080      _this.el().appendChild(_this.endDialog);
21081
21082      _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
21083
21084
21085      if (options.persistTextTrackSettings === undefined) {
21086        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
21087      }
21088
21089      _this.on(_this.$('.vjs-done-button'), 'click', function () {
21090        _this.saveSettings();
21091
21092        _this.close();
21093      });
21094
21095      _this.on(_this.$('.vjs-default-button'), 'click', function () {
21096        _this.setDefaults();
21097
21098        _this.updateDisplay();
21099      });
21100
21101      each(selectConfigs, function (config) {
21102        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
21103      });
21104
21105      if (_this.options_.persistTextTrackSettings) {
21106        _this.restoreSettings();
21107      }
21108
21109      return _this;
21110    }
21111
21112    var _proto = TextTrackSettings.prototype;
21113
21114    _proto.dispose = function dispose() {
21115      this.endDialog = null;
21116
21117      _ModalDialog.prototype.dispose.call(this);
21118    }
21119    /**
21120     * Create a <select> element with configured options.
21121     *
21122     * @param {string} key
21123     *        Configuration key to use during creation.
21124     *
21125     * @return {string}
21126     *         An HTML string.
21127     *
21128     * @private
21129     */
21130    ;
21131
21132    _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
21133      var _this2 = this;
21134
21135      if (legendId === void 0) {
21136        legendId = '';
21137      }
21138
21139      if (type === void 0) {
21140        type = 'label';
21141      }
21142
21143      var config = selectConfigs[key];
21144      var id = config.id.replace('%s', this.id_);
21145      var selectLabelledbyIds = [legendId, id].join(' ').trim();
21146      return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
21147        var optionId = id + '-' + o[1].replace(/\W+/g, '');
21148        return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
21149      })).concat('</select>').join('');
21150    }
21151    /**
21152     * Create foreground color element for the component
21153     *
21154     * @return {string}
21155     *         An HTML string.
21156     *
21157     * @private
21158     */
21159    ;
21160
21161    _proto.createElFgColor_ = function createElFgColor_() {
21162      var legendId = "captions-text-legend-" + this.id_;
21163      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('');
21164    }
21165    /**
21166     * Create background color element for the component
21167     *
21168     * @return {string}
21169     *         An HTML string.
21170     *
21171     * @private
21172     */
21173    ;
21174
21175    _proto.createElBgColor_ = function createElBgColor_() {
21176      var legendId = "captions-background-" + this.id_;
21177      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('');
21178    }
21179    /**
21180     * Create window color element for the component
21181     *
21182     * @return {string}
21183     *         An HTML string.
21184     *
21185     * @private
21186     */
21187    ;
21188
21189    _proto.createElWinColor_ = function createElWinColor_() {
21190      var legendId = "captions-window-" + this.id_;
21191      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('');
21192    }
21193    /**
21194     * Create color elements for the component
21195     *
21196     * @return {Element}
21197     *         The element that was created
21198     *
21199     * @private
21200     */
21201    ;
21202
21203    _proto.createElColors_ = function createElColors_() {
21204      return createEl('div', {
21205        className: 'vjs-track-settings-colors',
21206        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
21207      });
21208    }
21209    /**
21210     * Create font elements for the component
21211     *
21212     * @return {Element}
21213     *         The element that was created.
21214     *
21215     * @private
21216     */
21217    ;
21218
21219    _proto.createElFont_ = function createElFont_() {
21220      return createEl('div', {
21221        className: 'vjs-track-settings-font',
21222        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('')
21223      });
21224    }
21225    /**
21226     * Create controls for the component
21227     *
21228     * @return {Element}
21229     *         The element that was created.
21230     *
21231     * @private
21232     */
21233    ;
21234
21235    _proto.createElControls_ = function createElControls_() {
21236      var defaultsDescription = this.localize('restore all settings to the default values');
21237      return createEl('div', {
21238        className: 'vjs-track-settings-controls',
21239        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('')
21240      });
21241    };
21242
21243    _proto.content = function content() {
21244      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
21245    };
21246
21247    _proto.label = function label() {
21248      return this.localize('Caption Settings Dialog');
21249    };
21250
21251    _proto.description = function description() {
21252      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
21253    };
21254
21255    _proto.buildCSSClass = function buildCSSClass() {
21256      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
21257    }
21258    /**
21259     * Gets an object of text track settings (or null).
21260     *
21261     * @return {Object}
21262     *         An object with config values parsed from the DOM or localStorage.
21263     */
21264    ;
21265
21266    _proto.getValues = function getValues() {
21267      var _this3 = this;
21268
21269      return reduce(selectConfigs, function (accum, config, key) {
21270        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
21271
21272        if (value !== undefined) {
21273          accum[key] = value;
21274        }
21275
21276        return accum;
21277      }, {});
21278    }
21279    /**
21280     * Sets text track settings from an object of values.
21281     *
21282     * @param {Object} values
21283     *        An object with config values parsed from the DOM or localStorage.
21284     */
21285    ;
21286
21287    _proto.setValues = function setValues(values) {
21288      var _this4 = this;
21289
21290      each(selectConfigs, function (config, key) {
21291        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
21292      });
21293    }
21294    /**
21295     * Sets all `<select>` elements to their default values.
21296     */
21297    ;
21298
21299    _proto.setDefaults = function setDefaults() {
21300      var _this5 = this;
21301
21302      each(selectConfigs, function (config) {
21303        var index = config.hasOwnProperty('default') ? config["default"] : 0;
21304        _this5.$(config.selector).selectedIndex = index;
21305      });
21306    }
21307    /**
21308     * Restore texttrack settings from localStorage
21309     */
21310    ;
21311
21312    _proto.restoreSettings = function restoreSettings() {
21313      var values;
21314
21315      try {
21316        values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY$1));
21317      } catch (err) {
21318        log$1.warn(err);
21319      }
21320
21321      if (values) {
21322        this.setValues(values);
21323      }
21324    }
21325    /**
21326     * Save text track settings to localStorage
21327     */
21328    ;
21329
21330    _proto.saveSettings = function saveSettings() {
21331      if (!this.options_.persistTextTrackSettings) {
21332        return;
21333      }
21334
21335      var values = this.getValues();
21336
21337      try {
21338        if (Object.keys(values).length) {
21339          window_1.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
21340        } else {
21341          window_1.localStorage.removeItem(LOCAL_STORAGE_KEY$1);
21342        }
21343      } catch (err) {
21344        log$1.warn(err);
21345      }
21346    }
21347    /**
21348     * Update display of text track settings
21349     */
21350    ;
21351
21352    _proto.updateDisplay = function updateDisplay() {
21353      var ttDisplay = this.player_.getChild('textTrackDisplay');
21354
21355      if (ttDisplay) {
21356        ttDisplay.updateDisplay();
21357      }
21358    }
21359    /**
21360     * conditionally blur the element and refocus the captions button
21361     *
21362     * @private
21363     */
21364    ;
21365
21366    _proto.conditionalBlur_ = function conditionalBlur_() {
21367      this.previouslyActiveEl_ = null;
21368      var cb = this.player_.controlBar;
21369      var subsCapsBtn = cb && cb.subsCapsButton;
21370      var ccBtn = cb && cb.captionsButton;
21371
21372      if (subsCapsBtn) {
21373        subsCapsBtn.focus();
21374      } else if (ccBtn) {
21375        ccBtn.focus();
21376      }
21377    };
21378
21379    return TextTrackSettings;
21380  }(ModalDialog);
21381
21382  Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
21383
21384  /**
21385   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
21386   *
21387   * 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}.
21388   *
21389   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
21390   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
21391   * @example <caption>How to disable the resize manager</caption>
21392   * const player = videojs('#vid', {
21393   *   resizeManager: false
21394   * });
21395   *
21396   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
21397   *
21398   * @extends Component
21399   */
21400
21401  var ResizeManager = /*#__PURE__*/function (_Component) {
21402    inheritsLoose(ResizeManager, _Component);
21403
21404    /**
21405     * Create the ResizeManager.
21406     *
21407     * @param {Object} player
21408     *        The `Player` that this class should be attached to.
21409     *
21410     * @param {Object} [options]
21411     *        The key/value store of ResizeManager options.
21412     *
21413     * @param {Object} [options.ResizeObserver]
21414     *        A polyfill for ResizeObserver can be passed in here.
21415     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
21416     */
21417    function ResizeManager(player, options) {
21418      var _this;
21419
21420      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
21421
21422      if (options.ResizeObserver === null) {
21423        RESIZE_OBSERVER_AVAILABLE = false;
21424      } // Only create an element when ResizeObserver isn't available
21425
21426
21427      var options_ = mergeOptions$3({
21428        createEl: !RESIZE_OBSERVER_AVAILABLE,
21429        reportTouchActivity: false
21430      }, options);
21431      _this = _Component.call(this, player, options_) || this;
21432      _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver;
21433      _this.loadListener_ = null;
21434      _this.resizeObserver_ = null;
21435      _this.debouncedHandler_ = debounce(function () {
21436        _this.resizeHandler();
21437      }, 100, false, assertThisInitialized(_this));
21438
21439      if (RESIZE_OBSERVER_AVAILABLE) {
21440        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
21441
21442        _this.resizeObserver_.observe(player.el());
21443      } else {
21444        _this.loadListener_ = function () {
21445          if (!_this.el_ || !_this.el_.contentWindow) {
21446            return;
21447          }
21448
21449          var debouncedHandler_ = _this.debouncedHandler_;
21450
21451          var unloadListener_ = _this.unloadListener_ = function () {
21452            off(this, 'resize', debouncedHandler_);
21453            off(this, 'unload', unloadListener_);
21454            unloadListener_ = null;
21455          }; // safari and edge can unload the iframe before resizemanager dispose
21456          // we have to dispose of event handlers correctly before that happens
21457
21458
21459          on(_this.el_.contentWindow, 'unload', unloadListener_);
21460          on(_this.el_.contentWindow, 'resize', debouncedHandler_);
21461        };
21462
21463        _this.one('load', _this.loadListener_);
21464      }
21465
21466      return _this;
21467    }
21468
21469    var _proto = ResizeManager.prototype;
21470
21471    _proto.createEl = function createEl() {
21472      return _Component.prototype.createEl.call(this, 'iframe', {
21473        className: 'vjs-resize-manager',
21474        tabIndex: -1
21475      }, {
21476        'aria-hidden': 'true'
21477      });
21478    }
21479    /**
21480     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
21481     *
21482     * @fires Player#playerresize
21483     */
21484    ;
21485
21486    _proto.resizeHandler = function resizeHandler() {
21487      /**
21488       * Called when the player size has changed
21489       *
21490       * @event Player#playerresize
21491       * @type {EventTarget~Event}
21492       */
21493      // make sure player is still around to trigger
21494      // prevents this from causing an error after dispose
21495      if (!this.player_ || !this.player_.trigger) {
21496        return;
21497      }
21498
21499      this.player_.trigger('playerresize');
21500    };
21501
21502    _proto.dispose = function dispose() {
21503      if (this.debouncedHandler_) {
21504        this.debouncedHandler_.cancel();
21505      }
21506
21507      if (this.resizeObserver_) {
21508        if (this.player_.el()) {
21509          this.resizeObserver_.unobserve(this.player_.el());
21510        }
21511
21512        this.resizeObserver_.disconnect();
21513      }
21514
21515      if (this.loadListener_) {
21516        this.off('load', this.loadListener_);
21517      }
21518
21519      if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
21520        this.unloadListener_.call(this.el_.contentWindow);
21521      }
21522
21523      this.ResizeObserver = null;
21524      this.resizeObserver = null;
21525      this.debouncedHandler_ = null;
21526      this.loadListener_ = null;
21527
21528      _Component.prototype.dispose.call(this);
21529    };
21530
21531    return ResizeManager;
21532  }(Component$1);
21533
21534  Component$1.registerComponent('ResizeManager', ResizeManager);
21535
21536  var defaults = {
21537    trackingThreshold: 30,
21538    liveTolerance: 15
21539  };
21540  /*
21541    track when we are at the live edge, and other helpers for live playback */
21542
21543  /**
21544   * A class for checking live current time and determining when the player
21545   * is at or behind the live edge.
21546   */
21547
21548  var LiveTracker = /*#__PURE__*/function (_Component) {
21549    inheritsLoose(LiveTracker, _Component);
21550
21551    /**
21552     * Creates an instance of this class.
21553     *
21554     * @param {Player} player
21555     *        The `Player` that this class should be attached to.
21556     *
21557     * @param {Object} [options]
21558     *        The key/value store of player options.
21559     *
21560     * @param {number} [options.trackingThreshold=30]
21561     *        Number of seconds of live window (seekableEnd - seekableStart) that
21562     *        media needs to have before the liveui will be shown.
21563     *
21564     * @param {number} [options.liveTolerance=15]
21565     *        Number of seconds behind live that we have to be
21566     *        before we will be considered non-live. Note that this will only
21567     *        be used when playing at the live edge. This allows large seekable end
21568     *        changes to not effect wether we are live or not.
21569     */
21570    function LiveTracker(player, options) {
21571      var _this;
21572
21573      // LiveTracker does not need an element
21574      var options_ = mergeOptions$3(defaults, options, {
21575        createEl: false
21576      });
21577      _this = _Component.call(this, player, options_) || this;
21578
21579      _this.handleVisibilityChange_ = function (e) {
21580        return _this.handleVisibilityChange(e);
21581      };
21582
21583      _this.trackLiveHandler_ = function () {
21584        return _this.trackLive_();
21585      };
21586
21587      _this.handlePlay_ = function (e) {
21588        return _this.handlePlay(e);
21589      };
21590
21591      _this.handleFirstTimeupdate_ = function (e) {
21592        return _this.handleFirstTimeupdate(e);
21593      };
21594
21595      _this.handleSeeked_ = function (e) {
21596        return _this.handleSeeked(e);
21597      };
21598
21599      _this.seekToLiveEdge_ = function (e) {
21600        return _this.seekToLiveEdge(e);
21601      };
21602
21603      _this.reset_();
21604
21605      _this.on(_this.player_, 'durationchange', function (e) {
21606        return _this.handleDurationchange(e);
21607      }); // we should try to toggle tracking on canplay as native playback engines, like Safari
21608      // may not have the proper values for things like seekableEnd until then
21609
21610
21611      _this.one(_this.player_, 'canplay', function () {
21612        return _this.toggleTracking();
21613      }); // we don't need to track live playback if the document is hidden,
21614      // also, tracking when the document is hidden can
21615      // cause the CPU to spike and eventually crash the page on IE11.
21616
21617
21618      if (IE_VERSION && 'hidden' in document_1 && 'visibilityState' in document_1) {
21619        _this.on(document_1, 'visibilitychange', _this.handleVisibilityChange_);
21620      }
21621
21622      return _this;
21623    }
21624    /**
21625     * toggle tracking based on document visiblility
21626     */
21627
21628
21629    var _proto = LiveTracker.prototype;
21630
21631    _proto.handleVisibilityChange = function handleVisibilityChange() {
21632      if (this.player_.duration() !== Infinity) {
21633        return;
21634      }
21635
21636      if (document_1.hidden) {
21637        this.stopTracking();
21638      } else {
21639        this.startTracking();
21640      }
21641    }
21642    /**
21643     * all the functionality for tracking when seek end changes
21644     * and for tracking how far past seek end we should be
21645     */
21646    ;
21647
21648    _proto.trackLive_ = function trackLive_() {
21649      var seekable = this.player_.seekable();
21649 // skip undefined seekable
21650
21651      if (!seekable || !seekable.length) {
21652        return;
21653      }
21654
21655      var newTime = Number(window_1.performance.now().toFixed(4));
21656      var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
21657      this.lastTime_ = newTime;
21658      this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
21659      var liveCurrentTime = this.liveCurrentTime();
21660      var currentTime = this.player_.currentTime(); // we are behind live if any are true
21661      // 1. the player is paused
21662      // 2. the user seeked to a location 2 seconds away from live
21663      // 3. the difference between live and current time is greater
21664      //    liveTolerance which defaults to 15s
21665
21666      var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if
21667      // 1. until we have not seen a timeupdate yet
21668      // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
21669
21670      if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
21671        isBehind = false;
21672      }
21673
21674      if (isBehind !== this.behindLiveEdge_) {
21675        this.behindLiveEdge_ = isBehind;
21676        this.trigger('liveedgechange');
21677      }
21678    }
21679    /**
21680     * handle a durationchange event on the player
21681     * and start/stop tracking accordingly.
21682     */
21683    ;
21684
21685    _proto.handleDurationchange = function handleDurationchange() {
21686      this.toggleTracking();
21687    }
21688    /**
21689     * start/stop tracking
21690     */
21691    ;
21692
21693    _proto.toggleTracking = function toggleTracking() {
21694      if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
21695        if (this.player_.options_.liveui) {
21696          this.player_.addClass('vjs-liveui');
21697        }
21698
21699        this.startTracking();
21700      } else {
21701        this.player_.removeClass('vjs-liveui');
21702        this.stopTracking();
21703      }
21704    }
21705    /**
21706     * start tracking live playback
21707     */
21708    ;
21709
21710    _proto.startTracking = function startTracking() {
21711      if (this.isTracking()) {
21712        return;
21713      } // If we haven't seen a timeupdate, we need to check whether playback
21714      // began before this component started tracking. This can happen commonly
21715      // when using autoplay.
21716
21717
21718      if (!this.timeupdateSeen_) {
21719        this.timeupdateSeen_ = this.player_.hasStarted();
21720      }
21721
21722      this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
21723      this.trackLive_();
21724      this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
21725
21726      if (!this.timeupdateSeen_) {
21727        this.one(this.player_, 'play', this.handlePlay_);
21728        this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
21729      } else {
21730        this.on(this.player_, 'seeked', this.handleSeeked_);
21731      }
21732    }
21733    /**
21734     * handle the first timeupdate on the player if it wasn't already playing
21735     * when live tracker started tracking.
21736     */
21737    ;
21738
21739    _proto.handleFirstTimeupdate = function handleFirstTimeupdate() {
21740      this.timeupdateSeen_ = true;
21741      this.on(this.player_, 'seeked', this.handleSeeked_);
21742    }
21743    /**
21744     * Keep track of what time a seek starts, and listen for seeked
21745     * to find where a seek ends.
21746     */
21747    ;
21748
21749    _proto.handleSeeked = function handleSeeked() {
21750      var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
21751      this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
21752      this.nextSeekedFromUser_ = false;
21753      this.trackLive_();
21754    }
21755    /**
21756     * handle the first play on the player, and make sure that we seek
21757     * right to the live edge.
21758     */
21759    ;
21760
21761    _proto.handlePlay = function handlePlay() {
21762      this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
21763    }
21764    /**
21765     * Stop tracking, and set all internal variables to
21766     * their initial value.
21767     */
21768    ;
21769
21770    _proto.reset_ = function reset_() {
21771      this.lastTime_ = -1;
21772      this.pastSeekEnd_ = 0;
21773      this.lastSeekEnd_ = -1;
21774      this.behindLiveEdge_ = true;
21775      this.timeupdateSeen_ = false;
21776      this.seekedBehindLive_ = false;
21777      this.nextSeekedFromUser_ = false;
21778      this.clearInterval(this.trackingInterval_);
21779      this.trackingInterval_ = null;
21780      this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
21781      this.off(this.player_, 'seeked', this.handleSeeked_);
21782      this.off(this.player_, 'play', this.handlePlay_);
21783      this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
21784      this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
21785    }
21786    /**
21787     * The next seeked event is from the user. Meaning that any seek
21788     * > 2s behind live will be considered behind live for real and
21789     * liveTolerance will be ignored.
21790     */
21791    ;
21792
21793    _proto.nextSeekedFromUser = function nextSeekedFromUser() {
21794      this.nextSeekedFromUser_ = true;
21795    }
21796    /**
21797     * stop tracking live playback
21798     */
21799    ;
21800
21801    _proto.stopTracking = function stopTracking() {
21802      if (!this.isTracking()) {
21803        return;
21804      }
21805
21806      this.reset_();
21807      this.trigger('liveedgechange');
21808    }
21809    /**
21810     * A helper to get the player seekable end
21811     * so that we don't have to null check everywhere
21812     *
21813     * @return {number}
21814     *         The furthest seekable end or Infinity.
21815     */
21816    ;
21817
21818    _proto.seekableEnd = function seekableEnd() {
21819      var seekable = this.player_.seekable();
21820      var seekableEnds = [];
21821      var i = seekable ? seekable.length : 0;
21822
21823      while (i--) {
21824        seekableEnds.push(seekable.end(i));
21825      } // grab the furthest seekable end after sorting, or if there are none
21826      // default to Infinity
21827
21828
21829      return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
21830    }
21831    /**
21832     * A helper to get the player seekable start
21833     * so that we don't have to null check everywhere
21834     *
21835     * @return {number}
21836     *         The earliest seekable start or 0.
21837     */
21838    ;
21839
21840    _proto.seekableStart = function seekableStart() {
21841      var seekable = this.player_.seekable();
21842      var seekableStarts = [];
21843      var i = seekable ? seekable.length : 0;
21844
21845      while (i--) {
21846        seekableStarts.push(seekable.start(i));
21847      } // grab the first seekable start after sorting, or if there are none
21848      // default to 0
21849
21850
21851      return seekableStarts.length ? seekableStarts.sort()[0] : 0;
21852    }
21853    /**
21854     * Get the live time window aka
21855     * the amount of time between seekable start and
21856     * live current time.
21857     *
21858     * @return {number}
21859     *         The amount of seconds that are seekable in
21860     *         the live video.
21861     */
21862    ;
21863
21864    _proto.liveWindow = function liveWindow() {
21865      var liveCurrentTime = this.liveCurrentTime(); // if liveCurrenTime is Infinity then we don't have a liveWindow at all
21866
21867      if (liveCurrentTime === Infinity) {
21868        return 0;
21869      }
21870
21871      return liveCurrentTime - this.seekableStart();
21872    }
21873    /**
21874     * Determines if the player is live, only checks if this component
21875     * is tracking live playback or not
21876     *
21877     * @return {boolean}
21878     *         Wether liveTracker is tracking
21879     */
21880    ;
21881
21882    _proto.isLive = function isLive() {
21883      return this.isTracking();
21884    }
21885    /**
21886     * Determines if currentTime is at the live edge and won't fall behind
21887     * on each seekableendchange
21888     *
21889     * @return {boolean}
21890     *         Wether playback is at the live edge
21891     */
21892    ;
21893
21894    _proto.atLiveEdge = function atLiveEdge() {
21895      return !this.behindLiveEdge();
21896    }
21897    /**
21898     * get what we expect the live current time to be
21899     *
21900     * @return {number}
21901     *         The expected live current time
21902     */
21903    ;
21904
21905    _proto.liveCurrentTime = function liveCurrentTime() {
21906      return this.pastSeekEnd() + this.seekableEnd();
21907    }
21908    /**
21909     * The number of seconds that have occured after seekable end
21910     * changed. This will be reset to 0 once seekable end changes.
21911     *
21912     * @return {number}
21913     *         Seconds past the current seekable end
21914     */
21915    ;
21916
21917    _proto.pastSeekEnd = function pastSeekEnd() {
21918      var seekableEnd = this.seekableEnd();
21919
21920      if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
21921        this.pastSeekEnd_ = 0;
21922      }
21923
21924      this.lastSeekEnd_ = seekableEnd;
21925      return this.pastSeekEnd_;
21926    }
21927    /**
21928     * If we are currently behind the live edge, aka currentTime will be
21929     * behind on a seekableendchange
21930     *
21931     * @return {boolean}
21932     *         If we are behind the live edge
21933     */
21934    ;
21935
21936    _proto.behindLiveEdge = function behindLiveEdge() {
21937      return this.behindLiveEdge_;
21938    }
21939    /**
21940     * Wether live tracker is currently tracking or not.
21941     */
21942    ;
21943
21944    _proto.isTracking = function isTracking() {
21945      return typeof this.trackingInterval_ === 'number';
21946    }
21947    /**
21948     * Seek to the live edge if we are behind the live edge
21949     */
21950    ;
21951
21952    _proto.seekToLiveEdge = function seekToLiveEdge() {
21953      this.seekedBehindLive_ = false;
21954
21955      if (this.atLiveEdge()) {
21956        return;
21957      }
21958
21959      this.nextSeekedFromUser_ = false;
21960      this.player_.currentTime(this.liveCurrentTime());
21961    }
21962    /**
vendor: 6,466 bytes, lines 21963-22153
21963     * Dispose of liveTracker
21964     */
21965    ;
21966
21967    _proto.dispose = function dispose() {
21968      this.off(document_1, 'visibilitychange', this.handleVisibilityChange_);
21969      this.stopTracking();
21970
21971      _Component.prototype.dispose.call(this);
21972    };
21973
21974    return LiveTracker;
21975  }(Component$1);
21976
21977  Component$1.registerComponent('LiveTracker', LiveTracker);
21978
21979  /**
21980   * This function is used to fire a sourceset when there is something
21981   * similar to `mediaEl.load()` being called. It will try to find the source via
21982   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
21983   * with the source that was found or empty string if we cannot know. If it cannot
21984   * find a source then `sourceset` will not be fired.
21985   *
21986   * @param {Html5} tech
21987   *        The tech object that sourceset was setup on
21988   *
21989   * @return {boolean}
21990   *         returns false if the sourceset was not fired and true otherwise.
21991   */
21992
21993  var sourcesetLoad = function sourcesetLoad(tech) {
21994    var el = tech.el(); // if `el.src` is set, that source will be loaded.
21995
21996    if (el.hasAttribute('src')) {
21997      tech.triggerSourceset(el.src);
21998      return true;
21999    }
22000    /**
22001     * Since there isn't a src property on the media element, source elements will be used for
22002     * implementing the source selection algorithm. This happens asynchronously and
22003     * for most cases were there is more than one source we cannot tell what source will
22004     * be loaded, without re-implementing the source selection algorithm. At this time we are not
22005     * going to do that. There are three special cases that we do handle here though:
22006     *
22007     * 1. If there are no sources, do not fire `sourceset`.
22008     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
22009     * 3. If there is more than one `<source>` but all of them have the same `src` url.
22010     *    That will be our src.
22011     */
22012
22013
22014    var sources = tech.$$('source');
22015    var srcUrls = [];
22016    var src = ''; // if there are no sources, do not fire sourceset
22017
22018    if (!sources.length) {
22019      return false;
22020    } // only count valid/non-duplicate source elements
22021
22022
22023    for (var i = 0; i < sources.length; i++) {
22024      var url = sources[i].src;
22025
22026      if (url && srcUrls.indexOf(url) === -1) {
22027        srcUrls.push(url);
22028      }
22029    } // there were no valid sources
22030
22031
22032    if (!srcUrls.length) {
22033      return false;
22034    } // there is only one valid source element url
22035    // use that
22036
22037
22038    if (srcUrls.length === 1) {
22039      src = srcUrls[0];
22040    }
22041
22042    tech.triggerSourceset(src);
22043    return true;
22044  };
22045  /**
22046   * our implementation of an `innerHTML` descriptor for browsers
22047   * that do not have one.
22048   */
22049
22050
22051  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
22052    get: function get() {
22053      return this.cloneNode(true).innerHTML;
22054    },
22055    set: function set(v) {
22056      // make a dummy node to use innerHTML on
22057      var dummy = document_1.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
22058
22059      dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
22060
22061      var docFrag = document_1.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
22062      // to the document fragment
22063
22064      while (dummy.childNodes.length) {
22065        docFrag.appendChild(dummy.childNodes[0]);
22066      } // remove content
22067
22068
22069      this.innerText = ''; // now we add all of that html in one by appending the
22070      // document fragment. This is how innerHTML does it.
22071
22072      window_1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
22073
22074      return this.innerHTML;
22075    }
22076  });
22077  /**
22078   * Get a property descriptor given a list of priorities and the
22079   * property to get.
22080   */
22081
22082  var getDescriptor = function getDescriptor(priority, prop) {
22083    var descriptor = {};
22084
22085    for (var i = 0; i < priority.length; i++) {
22086      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
22087
22088      if (descriptor && descriptor.set && descriptor.get) {
22089        break;
22090      }
22091    }
22092
22093    descriptor.enumerable = true;
22094    descriptor.configurable = true;
22095    return descriptor;
22096  };
22097
22098  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
22099    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
22100  };
22101  /**
22102   * Patches browser internal functions so that we can tell synchronously
22103   * if a `<source>` was appended to the media element. For some reason this
22104   * causes a `sourceset` if the the media element is ready and has no source.
22105   * This happens when:
22106   * - The page has just loaded and the media element does not have a source.
22107   * - The media element was emptied of all sources, then `load()` was called.
22108   *
22109   * It does this by patching the following functions/properties when they are supported:
22110   *
22111   * - `append()` - can be used to add a `<source>` element to the media element
22112   * - `appendChild()` - can be used to add a `<source>` element to the media element
22113   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
22114   * - `innerHTML` -  can be used to add a `<source>` element to the media element
22115   *
22116   * @param {Html5} tech
22117   *        The tech object that sourceset is being setup on.
22118   */
22119
22120
22121  var firstSourceWatch = function firstSourceWatch(tech) {
22122    var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
22123
22124    if (el.resetSourceWatch_) {
22125      return;
22126    }
22127
22128    var old = {};
22129    var innerDescriptor = getInnerHTMLDescriptor(tech);
22130
22131    var appendWrapper = function appendWrapper(appendFn) {
22132      return function () {
22133        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
22134          args[_key] = arguments[_key];
22135        }
22136
22137        var retval = appendFn.apply(el, args);
22138        sourcesetLoad(tech);
22139        return retval;
22140      };
22141    };
22142
22143    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
22144      if (!el[k]) {
22145        return;
22146      } // store the old function
22147
22148
22149      old[k] = el[k]; // call the old function with a sourceset if a source
22150      // was loaded
22151
22152      el[k] = appendWrapper(old[k]);
22153    });
22154    Object.defineProperty(el, 'innerHTML', mergeOptions$3(innerDescriptor, {
22155      set: appendWrapper(innerDescriptor.set)
22156    }));
22157
22158    el.resetSourceWatch_ = function () {
22159      el.resetSourceWatch_ = null;
22160      Object.keys(old).forEach(function (k) {
22161        el[k] = old[k];
22162      });
22163      Object.defineProperty(el, 'innerHTML', innerDescriptor);
22164    }; // on the first sourceset, we need to revert our changes
22165
22166
22167    tech.one('sourceset', el.resetSourceWatch_);
22168  };
22169  /**
22170   * our implementation of a `src` descriptor for browsers
22171   * that do not have one.
22172   */
22173
22174
22175  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
22176    get: function get() {
22177      if (this.hasAttribute('src')) {
22178        return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src'));
22179      }
22180
22181      return '';
22182    },
22183    set: function set(v) {
22184      window_1.Element.prototype.setAttribute.call(this, 'src', v);
22185      return v;
22186    }
22187  });
22188
22189  var getSrcDescriptor = function getSrcDescriptor(tech) {
22190    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
22191  };
22192  /**
22193   * setup `sourceset` handling on the `Html5` tech. This function
22194   * patches the following element properties/functions:
22195   *
22196   * - `src` - to determine when `src` is set
22197   * - `setAttribute()` - to determine when `src` is set
22198   * - `load()` - this re-triggers the source selection algorithm, and can
22199   *              cause a sourceset.
22200   *
22201   * If there is no source when we are adding `sourceset` support or during a `load()`
22202   * we also patch the functions listed in `firstSourceWatch`.
22203   *
22204   * @param {Html5} tech
22205   *        The tech to patch
22206   */
22207
22208
22209  var setupSourceset = function setupSourceset(tech) {
22210    if (!tech.featuresSourceset) {
22211      return;
22212    }
22213
22214    var el = tech.el(); // make sure sourceset isn't setup twice.
22215
22216    if (el.resetSourceset_) {
22217      return;
22218    }
22219
22220    var srcDescriptor = getSrcDescriptor(tech);
22221    var oldSetAttribute = el.setAttribute;
22222    var oldLoad = el.load;
22223    Object.defineProperty(el, 'src', mergeOptions$3(srcDescriptor, {
22224      set: function set(v) {
22225        var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
22226
22227        tech.triggerSourceset(el.src);
22228        return retval;
22229      }
22230    }));
22231
22232    el.setAttribute = function (n, v) {
22233      var retval = oldSetAttribute.call(el, n, v);
22234
22235      if (/src/i.test(n)) {
22236        tech.triggerSourceset(el.src);
22237      }
22238
22239      return retval;
22240    };
22241
22242    el.load = function () {
22243      var retval = oldLoad.call(el); // if load was called, but there was no source to fire
22244      // sourceset on. We have to watch for a source append
22245      // as that can trigger a `sourceset` when the media element
22246      // has no source
22247
22248      if (!sourcesetLoad(tech)) {
22249        tech.triggerSourceset('');
22250        firstSourceWatch(tech);
22251      }
22252
22253      return retval;
22254    };
22255
22256    if (el.currentSrc) {
22257      tech.triggerSourceset(el.currentSrc);
22258    } else if (!sourcesetLoad(tech)) {
22259      firstSourceWatch(tech);
22260    }
22261
22262    el.resetSourceset_ = function () {
22263      el.resetSourceset_ = null;
22264      el.load = oldLoad;
22265      el.setAttribute = oldSetAttribute;
22266      Object.defineProperty(el, 'src', srcDescriptor);
22267
22268      if (el.resetSourceWatch_) {
22269        el.resetSourceWatch_();
22270      }
22271    };
22272  };
22273
22274  /**
22275   * Object.defineProperty but "lazy", which means that the value is only set after
22276   * it retrieved the first time, rather than being set right away.
22277   *
22278   * @param {Object} obj the object to set the property on
22279   * @param {string} key the key for the property to set
22280   * @param {Function} getValue the function used to get the value when it is needed.
22281   * @param {boolean} setter wether a setter shoould be allowed or not
22282   */
22283  var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) {
22284    if (setter === void 0) {
22285      setter = true;
22286    }
22287
22288    var set = function set(value) {
22289      return Object.defineProperty(obj, key, {
22290        value: value,
22291        enumerable: true,
22292        writable: true
22293      });
22294    };
22295
22296    var options = {
22297      configurable: true,
22298      enumerable: true,
22299      get: function get() {
22300        var value = getValue();
22301        set(value);
22302        return value;
22303      }
22304    };
22305
22306    if (setter) {
22307      options.set = set;
22308    }
22309
22310    return Object.defineProperty(obj, key, options);
22311  };
22312
22313  /**
22314   * HTML5 Media Controller - Wrapper for HTML5 Media API
22315   *
22316   * @mixes Tech~SourceHandlerAdditions
22317   * @extends Tech
22318   */
22319
22320  var Html5 = /*#__PURE__*/function (_Tech) {
22321    inheritsLoose(Html5, _Tech);
22322
22323    /**
22324    * Create an instance of this Tech.
22325    *
22326    * @param {Object} [options]
22327    *        The key/value store of player options.
22328    *
22329    * @param {Component~ReadyCallback} ready
22330    *        Callback function to call when the `HTML5` Tech is ready.
22331    */
22332    function Html5(options, ready) {
22333      var _this;
22334
22335      _this = _Tech.call(this, options, ready) || this;
22336      var source = options.source;
22337      var crossoriginTracks = false; // Set the source if one is provided
22338      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
22339      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
22340      // anyway so the error gets fired.
22341
22342      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.
vendor: 7,352 bytes, lines 22342-22531
22342initNetworkState_ === 3)) {
22343        _this.setSource(source);
22344      } else {
22345        _this.handleLateInit_(_this.el_);
22346      } // setup sourceset after late sourceset/init
22347
22348
22349      if (options.enableSourceset) {
22350        _this.setupSourcesetHandling_();
22351      }
22352
22353      _this.isScrubbing_ = false;
22354
22355      if (_this.el_.hasChildNodes()) {
22356        var nodes = _this.el_.childNodes;
22357        var nodesLength = nodes.length;
22358        var removeNodes = [];
22359
22360        while (nodesLength--) {
22361          var node = nodes[nodesLength];
22362          var nodeName = node.nodeName.toLowerCase();
22363
22364          if (nodeName === 'track') {
22365            if (!_this.featuresNativeTextTracks) {
22366              // Empty video tag tracks so the built-in player doesn't use them also.
22367              // This may not be fast enough to stop HTML5 browsers from reading the tags
22368              // so we'll need to turn off any default tracks if we're manually doing
22369              // captions and subtitles. videoElement.textTracks
22370              removeNodes.push(node);
22371            } else {
22372              // store HTMLTrackElement and TextTrack to remote list
22373              _this.remoteTextTrackEls().addTrackElement_(node);
22374
22375              _this.remoteTextTracks().addTrack(node.track);
22376
22377              _this.textTracks().addTrack(node.track);
22378
22379              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
22380                crossoriginTracks = true;
22381              }
22382            }
22383          }
22384        }
22385
22386        for (var i = 0; i < removeNodes.length; i++) {
22387          _this.el_.removeChild(removeNodes[i]);
22388        }
22389      }
22390
22391      _this.proxyNativeTracks_();
22392
22393      if (_this.featuresNativeTextTracks && crossoriginTracks) {
22394        log$1.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
22395      } // prevent iOS Safari from disabling metadata text tracks during native playback
22396
22397
22398      _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
22399      // Our goal should be to get the custom controls on mobile solid everywhere
22400      // so we can remove this all together. Right now this will block custom
22401      // controls on touch enabled laptops like the Chrome Pixel
22402
22403
22404      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
22405        _this.setControls(true);
22406      } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
22407      // into a `fullscreenchange` event
22408
22409
22410      _this.proxyWebkitFullscreen_();
22411
22412      _this.triggerReady();
22413
22414      return _this;
22415    }
22416    /**
22417     * Dispose of `HTML5` media element and remove all tracks.
22418     */
22419
22420
22421    var _proto = Html5.prototype;
22422
22423    _proto.dispose = function dispose() {
22424      if (this.el_ && this.el_.resetSourceset_) {
22425        this.el_.resetSourceset_();
22426      }
22427
22428      Html5.disposeMediaElement(this.el_);
22429      this.options_ = null; // tech will handle clearing of the emulated track list
22430
22431      _Tech.prototype.dispose.call(this);
22432    }
22433    /**
22434     * Modify the media element so that we can detect when
22435     * the source is changed. Fires `sourceset` just after the source has changed
22436     */
22437    ;
22438
22439    _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
22440      setupSourceset(this);
22441    }
22442    /**
22443     * When a captions track is enabled in the iOS Safari native player, all other
22444     * tracks are disabled (including metadata tracks), which nulls all of their
22445     * associated cue points. This will restore metadata tracks to their pre-fullscreen
22446     * state in those cases so that cue points are not needlessly lost.
22447     *
22448     * @private
22449     */
22450    ;
22451
22452    _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
22453      var textTracks = this.textTracks();
22454      var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
22455
22456      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
22457        metadataTracksPreFullscreenState = [];
22458
22459        for (var i = 0; i < textTracks.length; i++) {
22460          var track = textTracks[i];
22461
22462          if (track.kind === 'metadata') {
22463            metadataTracksPreFullscreenState.push({
22464              track: track,
22465              storedMode: track.mode
22466            });
22467          }
22468        }
22469      }; // snapshot each metadata track's initial state, and update the snapshot
22470      // each time there is a track 'change' event
22471
22472
22473      takeMetadataTrackSnapshot();
22474      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
22475      this.on('dispose', function () {
22476        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
22477      });
22478
22479      var restoreTrackMode = function restoreTrackMode() {
22480        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
22481          var storedTrack = metadataTracksPreFullscreenState[i];
22482
22483          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
22484            storedTrack.track.mode = storedTrack.storedMode;
22485          }
22486        } // we only want this handler to be executed on the first 'change' event
22487
22488
22489        textTracks.removeEventListener('change', restoreTrackMode);
22490      }; // when we enter fullscreen playback, stop updating the snapshot and
22491      // restore all track modes to their pre-fullscreen state
22492
22493
22494      this.on('webkitbeginfullscreen', function () {
22495        textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
22496
22497        textTracks.removeEventListener('change', restoreTrackMode);
22498        textTracks.addEventListener('change', restoreTrackMode);
22499      }); // start updating the snapshot again after leaving fullscreen
22500
22501      this.on('webkitendfullscreen', function () {
22502        // remove the listener before adding it just in case it wasn't previously removed
22503        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
22504        textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
22505
22506        textTracks.removeEventListener('change', restoreTrackMode);
22507      });
22508    }
22509    /**
22510     * Attempt to force override of tracks for the given type
22511     *
22512     * @param {string} type - Track type to override, possible values include 'Audio',
22513     * 'Video', and 'Text'.
22514     * @param {boolean} override - If set to true native audio/video will be overridden,
22515     * otherwise native audio/video will potentially be used.
22516     * @private
22517     */
22518    ;
22519
22520    _proto.overrideNative_ = function overrideNative_(type, override) {
22521      var _this2 = this;
22522
22523      // If there is no behavioral change don't add/remove listeners
22524      if (override !== this["featuresNative" + type + "Tracks"]) {
22525        return;
22526      }
22527
22528      var lowerCaseType = type.toLowerCase();
22529
22530      if (this[lowerCaseType + "TracksListeners_"]) {
22531        Object.keys(this[lowerCaseType + "TracksListeners_"]).forEac
22531h(function (eventName) {
22532          var elTracks = _this2.el()[lowerCaseType + "Tracks"];
22533
22534          elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
22535        });
22536      }
22537
22538      this["featuresNative" + type + "Tracks"] = !override;
22539      this[lowerCaseType + "TracksListeners_"] = null;
22540      this.proxyNativeTracksForType_(lowerCaseType);
22541    }
22542    /**
22543     * Attempt to force override of native audio tracks.
22544     *
22545     * @param {boolean} override - If set to true native audio will be overridden,
22546     * otherwise native audio will potentially be used.
22547     */
22548    ;
22549
22550    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
22551      this.overrideNative_('Audio', override);
22552    }
22553    /**
22554     * Attempt to force override of native video tracks.
22555     *
22556     * @param {boolean} override - If set to true native video will be overridden,
22557     * otherwise native video will potentially be used.
22558     */
22559    ;
22560
22561    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
22562      this.overrideNative_('Video', override);
22563    }
22564    /**
22565     * Proxy native track list events for the given type to our track
22566     * lists if the browser we are playing in supports that type of track list.
22567     *
22568     * @param {string} name - Track type; values include 'audio', 'video', and 'text'
22569     * @private
22570     */
22571    ;
22572
22573    _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
22574      var _this3 = this;
22575
22576      var props = NORMAL[name];
22577      var elTracks = this.el()[props.getterName];
22578      var techTracks = this[props.getterName]();
22579
22580      if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
22581        return;
22582      }
22583
22584      var listeners = {
22585        change: function change(e) {
22586          var event = {
22587            type: 'change',
22588            target: techTracks,
22589            currentTarget: techTracks,
22590            srcElement: techTracks
22591          };
22592          techTracks.trigger(event); // if we are a text track change event, we should also notify the
22593          // remote text track list. This can potentially cause a false positive
22594          // if we were to get a change event on a non-remote track and
22595          // we triggered the event on the remote text track list which doesn't
22596          // contain that track. However, best practices mean looping through the
22597          // list of tracks and searching for the appropriate mode value, so,
22598          // this shouldn't pose an issue
22599
22600          if (name === 'text') {
22601            _this3[REMOTE.remoteText.getterName]().trigger(event);
22602          }
22603        },
22604        addtrack: function addtrack(e) {
22605          techTracks.addTrack(e.track);
22606        },
22607        removetrack: function removetrack(e) {
22608          techTracks.removeTrack(e.track);
22609        }
22610      };
22611
22612      var removeOldTracks = function removeOldTracks() {
22613        var removeTracks = [];
22614
22615        for (var i = 0; i < techTracks.length; i++) {
22616          var found = false;
22617
22618          for (var j = 0; j < elTracks.length; j++) {
22619            if (elTracks[j] === techTracks[i]) {
22620              found = true;
22621              break;
22622            }
22623          }
22624
22625          if (!found) {
22626            removeTracks.push(techTracks[i]);
22627          }
22628        }
22629
22630        while (removeTracks.length) {
22631          techTracks.removeTrack(removeTracks.shift());
22632        }
22633      };
22634
22635      this[props.getterName + 'Listeners_'] = listeners;
22636      Object.keys(listeners).forEach(function (eventName) {
22637        var listener = listeners[eventName];
22638        elTracks.addEventListener(eventName, listener);
22639
22640        _this3.on('dispose', function (e) {
22641          return elTracks.removeEventListener(eventName, listener);
22642        });
22643      }); // Remove (native) tracks that are not used anymore
22644
22645      this.on('loadstart', removeOldTracks);
22646      this.on('dispose', function (e) {
22647        return _this3.off('loadstart', removeOldTracks);
22648      });
22649    }
22650    /**
22651     * Proxy all native track list events to our track lists if the browser we are playing
22652     * in supports that type of track list.
22653     *
22654     * @private
22655     */
22656    ;
22657
22658    _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
22659      var _this4 = this;
22660
22661      NORMAL.names.forEach(function (name) {
22662        _this4.proxyNativeTracksForType_(name);
22663      });
22664    }
22665    /**
22666     * Create the `Html5` Tech's DOM element.
22667     *
22668     * @return {Element}
22669     *         The element that gets created.
22670     */
22671    ;
22672
22673    _proto.createEl = function createEl() {
22674      var el = this.options_.tag; // Check if this browser supports moving the element into the box.
22675      // On the iPhone video will break if you move the element,
22676      // So we have to create a brand new element.
22677      // If we ingested the player div, we do not need to move the media element.
22678
22679      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
22680        // If the original tag is still there, clone and remove it.
22681        if (el) {
22682          var clone = el.cloneNode(true);
22683
22684          if (el.parentNode) {
22685            el.parentNode.insertBefore(clone, el);
22686          }
22687
22688          Html5.disposeMediaElement(el);
22689          el = clone;
22690        } else {
22691          el = document_1.createElement('video'); // determine if native controls should be used
22692
22693          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
22694          var attributes = mergeOptions$3({}, tagAttributes);
22695
22696          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
22697            delete attributes.controls;
22698          }
22699
22700          setAttributes(el, assign(attributes, {
22701            id: this.options_.techId,
22702            "class": 'vjs-tech'
22703          }));
22704        }
22705
22706        el.playerId = this.options_.playerId;
22707      }
22708
22709      if (typeof this.options_.preload !== 'undefined') {
22710        setAttribute(el, 'preload', this.options_.preload);
22711      }
22712
22713      if (this.options_.disablePictureInPicture !== undefined) {
22714        el.disablePictureInPicture = this.options_.disablePictureInPicture;
22715      } // Update specific tag settings, in case they were overridden
22716      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
22717      // when iOS/Safari or other browsers attempt to autoplay.
22718
22719
22720      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
22721
22722      for (var i = 0; i < settingsAttrs.length; i++) {
22723        var attr = settingsAttrs[i];
22724        var value = this.options_[attr];
22725
22726        if (typeof value !== 'undefined') {
22727          if (value) {
22728            setAttribute(el, attr, attr);
22729          } else {
22730            removeAttribute(el, attr);
22731          }
22732
22733          el[attr] = value;
22734        }
22735      }
22736
22737      return el;
22738    }
22739    /**
22740     * This will be triggered if the loadstart event has already fired, before videojs was
22741     * ready. Two known examples of when this can happen are:
22742     * 1. If we're loading the playback object after it has started loading
22743     * 2. The media is already playing the (often with autoplay on) then
22744     *
22745     * This function will fire another loadstart so that videojs can catchup.
22746     *
22747     * @fires Tech#loadstart
22748     *
22749     * @return {undefined}
22750     *         returns nothing.
22751     */
22752    ;
22753
22754    _proto.handleLateInit_ = function handleLateInit_(el) {
22755      if (el.networkState === 0 || el.networkState === 3) {
22756        // The video element hasn't started loading the source yet
22757        // or didn't find a source
22758        return;
22759      }
22760
22761      if (el.readyState === 0) {
22762        // NetworkState is set synchronously BUT loadstart is fired at the
22763        // end of the current stack, usually before setInterval(fn, 0).
22764        // So at this point we know loadstart may have already fired or is
22765        // about to fire, and either way the player hasn't seen it yet.
22766        // We don't want to fire loadstart prematurely here and cause a
22767        // double loadstart so we'll wait and see if it happens between now
22768        // and the next loop, and fire it if not.
22769        // HOWEVER, we also want to make sure it fires before loadedmetadata
22770        // which could also happen between now and the next loop, so we'll
22771        // watch for that also.
22772        var loadstartFired = false;
22773
22774        var setLoadstartFired = function setLoadstartFired() {
22775          loadstartFired = true;
22776        };
22777
22778        this.on('loadstart', setLoadstartFired);
22779
22780        var triggerLoadstart = function triggerLoadstart() {
22781          // We did miss the original loadstart. Make sure the player
22782          // sees loadstart before loadedmetadata
22783          if (!loadstartFired) {
22784            this.trigger('loadstart');
22785          }
22786        };
22787
22788        this.on('loadedmetadata', triggerLoadstart);
22789        this.ready(function () {
22790          this.off('loadstart', setLoadstartFired);
22791          this.off('loadedmetadata', triggerLoadstart);
22792
22793          if (!loadstartFired) {
22794            // We did miss the original native loadstart. Fire it now.
22795            this.trigger('loadstart');
22796          }
22797        });
22798        return;
22799      } // From here on we know that loadstart already fired and we missed it.
22800      // The other readyState events aren't as much of a problem if we double
22801      // them, so not going to go to as much trouble as loadstart to prevent
22802      // that unless we find reason to.
22803
22804
22805      var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
22806
22807      eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
22808
22809      if (el.readyState >= 2) {
22810        eventsToTrigger.push('loadeddata');
22811      } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
22812
22813
22814      if (el.readyState >= 3) {
22815        eventsToTrigger.push('canplay');
22816      } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
22817
22818
22819      if (el.readyState >= 4) {
22820        eventsToTrigger.push('canplaythrough');
22821      } // We still need to give the player time to add event listeners
22822
22823
22824      this.ready(function () {
22825        eventsToTrigger.forEach(function (type) {
22826          this.trigger(type);
22827        }, this);
22828      });
22829    }
22830    /**
22831     * Set whether we are scrubbing or not.
22832     * This is used to decide whether we should use `fastSeek` or not.
22833     * `fastSeek` is used to provide trick play on Safari browsers.
22834     *
22835     * @param {boolean} isScrubbing
22836     *                  - true for we are currently scrubbing
22837     *                  - false for we are no longer scrubbing
22838     */
22839    ;
22840
22841    _proto.setScrubbing = function setScrubbing(isScrubbing) {
22842      this.isScrubbing_ = isScrubbing;
22843    }
22844    /**
22845     * Get whether we are scrubbing or not.
22846     *
22847     * @return {boolean} isScrubbing
22848     *                  - true for we are currently scrubbing
22849     *                  - false for we are no longer scrubbing
22850     */
22851    ;
22852
22853    _proto.scrubbing = function scrubbing() {
22854      return this.isScrubbing_;
22855    }
22856    /**
22857     * Set current time for the `HTML5` tech.
22858     *
22859     * @param {number} seconds
22860     *        Set the current time of the media to this.
22861     */
22862    ;
22863
22864    _proto.setCurrentTime = function setCurrentTime(seconds) {
22865      try {
22866        if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
22867          this.el_.fastSeek(seconds);
22868        } else {
22869          this.el_.currentTime = seconds;
22870        }
22871      } catch (e) {
22872        log$1(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
22873      }
22874    }
22875    /**
22876     * Get the current duration of the HTML5 media element.
22877     *
22878     * @return {number}
22879     *         The duration of the media or 0 if there is no duration.
22880     */
22881    ;
22882
22883    _proto.duration = function duration() {
22884      var _this5 = this;
22885
22886      // Android Chrome will report duration as Infinity for VOD HLS until after
22887      // playback has started, which triggers the live display erroneously.
22888      // Return NaN if playback has not started and trigger a durationupdate once
22889      // the duration can be reliably known.
22890      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
22891        // Wait for the first `timeupdate` with currentTime > 0 - there may be
22892        // several with 0
22893        var checkProgress = function checkProgress() {
22894          if (_this5.el_.currentTime > 0) {
22895            // Trigger durationchange for genuinely live video
22896            if (_this5.el_.duration === Infinity) {
22897              _this5.trigger('durationchange');
22898            }
22899
22900            _this5.off('timeupdate', checkProgress);
22901          }
22902        };
22903
22904        this.on('timeupdate', checkProgress);
22905        return NaN;
22906      }
22907
22908      return this.el_.duration || NaN;
22909    }
22910    /**
22911     * Get the current width of the HTML5 media element.
22912     *
22913     * @return {number}
22914     *         The width of the HTML5 media element.
22915     */
22916    ;
22917
22918    _proto.width = function width() {
22919      return this.el_.offsetWidth;
22920    }
22921    /**
22922     * Get the current height of the HTML5 media element.
22923     *
22924     * @return {number}
22925     *         The height of the HTML5 media element.
22926     */
22927    ;
22928
22929    _proto.height = function height() {
22930      return this.el_.offsetHeight;
22931    }
22932    /**
22933     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
22934     * `fullscreenchange` event.
22935     *
22936     * @private
22937     * @fires fullscreenchange
22938     * @listens webkitendfullscreen
22939     * @listens webkitbeginfullscreen
22940     * @listens webkitbeginfullscreen
22941     */
22942    ;
22943
22944    _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
22945      var _this6 = this;
22946
22947      if (!('webkitDisplayingFullscreen' in this.el_)) {
22948        return;
22949      }
22950
22951      var endFn = function endFn() {
22952        this.trigger('fullscreenchange', {
22953          isFullscreen: false
22954        });
22955      };
22956
22957      var beginFn = function beginFn() {
22958        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
22959          this.one('webkitendfullscreen', endFn);
22960          this.trigger('fullscreenchange', {
22961            isFullscreen: true,
22962            // set a flag in case another tech triggers fullscreenchange
22963            nativeIOSFullscreen: true
22964          });
22965        }
22966      };
22967
22968      this.on('webkitbeginfullscreen', beginFn);
22969      this.on('dispose', function () {
22970        _this6.off('webkitbeginfullscreen', beginFn);
22971
22972        _this6.off('webkitendfullscreen', endFn);
22973      });
22974    }
22975    /**
22976     * Check if fullscreen is supported on the current playback device.
22977     *
22978     * @return {boolean}
22979     *         - True if fullscreen is supported.
22980     *         - False if fullscreen is not supported.
22981     */
22982    ;
22983
22984    _proto.supportsFullScreen = function supportsFullScreen() {
22985      if (typeof this.el_.webkitEnterFullScreen === 'function') {
22986        var userAgent = window_1.navigator && window_1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
22987
22988        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
22989          return true;
22990        }
22991      }
22992
22993      return false;
22994    }
22995    /**
22996     * Request that the `HTML5` Tech enter fullscreen.
22997     */
22998    ;
22999
23000    _proto.enterFullScreen = function enterFullScreen() {
23001      var video = this.el_;
23002
23003      if (video.paused && video.networkState <= video.HAVE_METADATA) {
23004        // attempt to prime the video element for programmatic access
23005        // this isn't necessary on the desktop but shouldn't hurt
23006        silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen
23007        // can get iOS ~6.1 devices into a play/pause loop
23008
23009        this.setTimeout(function () {
23010          video.pause();
23011
23012          try {
23013            video.webkitEnterFullScreen();
23014          } catch (e) {
23015            this.trigger('fullscreenerror', e);
23016          }
23017        }, 0);
23018      } else {
23019        try {
23020          video.webkitEnterFullScreen();
23021        } catch (e) {
23022          this.trigger('fullscreenerror', e);
23023        }
23024      }
23025    }
23026    /**
23027     * Request that the `HTML5` Tech exit fullscreen.
23028     */
23029    ;
23030
23031    _proto.exitFullScreen = function exitFullScreen() {
23032      if (!this.el_.webkitDisplayingFullscreen) {
23033        this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
23034        return;
23035      }
23036
23037      this.el_.webkitExitFullScreen();
23038    }
23039    /**
23040     * Create a floating video window always on top of other windows so that users may
23041     * continue consuming media while they interact with other content sites, or
23042     * applications on their device.
23043     *
23044     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
23045     *
23046     * @return {Promise}
23047     *         A promise with a Picture-in-Picture window.
23048     */
23049    ;
23050
23051    _proto.requestPictureInPicture = function requestPictureInPicture() {
23052      return this.el_.requestPictureInPicture();
23053    }
23054    /**
23055     * A getter/setter for the `Html5` Tech's source object.
23056     * > Note: Please use {@link Html5#setSource}
23057     *
23058     * @param {Tech~SourceObject} [src]
23059     *        The source object you want to set on the `HTML5` techs element.
23060     *
23061     * @return {Tech~SourceObject|undefined}
23062     *         - The current source object when a source is not passed in.
23063     *         - undefined when setting
23064     *
23065     * @deprecated Since version 5.
23066     */
23067    ;
23068
23069    _proto.src = function src(_src) {
23070      if (_src === undefined) {
23071        return this.el_.src;
23072      } // Setting src through `src` instead of `setSrc` will be deprecated
23073
23074
23075      this.setSrc(_src);
23076    }
23077    /**
23078     * Reset the tech by removing all sources and then calling
23079     * {@link Html5.resetMediaElement}.
23080     */
23081    ;
23082
23083    _proto.reset = function reset() {
23084      Html5.resetMediaElement(this.el_);
23085    }
23086    /**
23087     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
23088     * the HTML5 media element.
23089     *
23090     * @return {Tech~SourceObject}
23091     *         The current source object from the HTML5 tech. With a fallback to the
23092     *         elements source.
23093     */
23094    ;
23095
23096    _proto.currentSrc = function currentSrc() {
23097      if (this.currentSource_) {
23098        return this.currentSource_.src;
23099      }
23100
23101      return this.el_.currentSrc;
23102    }
23103    /**
23104     * Set controls attribute for the HTML5 media Element.
23105     *
23106     * @param {string} val
23107     *        Value to set the controls attribute to
23108     */
23109    ;
23110
23111    _proto.setControls = function setControls(val) {
23112      this.el_.controls = !!val;
23113    }
23114    /**
23115     * Create and returns a remote {@link TextTrack} object.
23116     *
23117     * @param {string} kind
23118     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
23119     *
23120     * @param {string} [label]
23121     *        Label to identify the text track
23122     *
23123     * @param {string} [language]
23124     *        Two letter language abbreviation
23125     *
23126     * @return {TextTrack}
23127     *         The TextTrack that gets created.
23128     */
23129    ;
23130
23131    _proto.addTextTrack = function addTextTrack(kind, label, language) {
23132      if (!this.featuresNativeTextTracks) {
23133        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
23134      }
23135
23136      return this.el_.addTextTrack(kind, label, language);
23137    }
23138    /**
23139     * Creates either native TextTrack or an emulated TextTrack depending
23140     * on the value of `featuresNativeTextTracks`
23141     *
23142     * @param {Object} options
23143     *        The object should contain the options to initialize the TextTrack with.
23144     *
23145     * @param {string} [options.kind]
23146     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
23147     *
23148     * @param {string} [options.label]
23149     *        Label to identify the text track
23150     *
23151     * @param {string} [options.language]
23152     *        Two letter language abbreviation.
23153     *
23154     * @param {boolean} [options.default]
23155     *        Default this track to on.
23156     *
23157     * @param {string} [options.id]
23158     *        The internal id to assign this track.
23159     *
23160     * @param {string} [options.src]
23161     *        A source url for the track.
23162     *
23163     * @return {HTMLTrackElement}
23164     *         The track element that gets created.
23165     */
23166    ;
23167
23168    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
23169      if (!this.featuresNativeTextTracks) {
23170        return _Tech.prototype.createRemoteTextTrack.call(this, options);
23171      }
23172
23173      var htmlTrackElement = document_1.createElement('track');
23174
23175      if (options.kind) {
23176        htmlTrackElement.kind = options.kind;
23177      }
23178
23179      if (options.label) {
23180        htmlTrackElement.label = options.label;
23181      }
23182
23183      if (options.language || options.srclang) {
23184        htmlTrackElement.srclang = options.language || options.srclang;
23185      }
23186
23187      if (options["default"]) {
23188        htmlTrackElement["default"] = options["default"];
23189      }
23190
23191      if (options.id) {
23192        htmlTrackElement.id = options.id;
23193      }
23194
23195      if (options.src) {
23196        htmlTrackElement.src = options.src;
23197      }
23198
23199      return htmlTrackElement;
23200    }
23201    /**
23202     * Creates a remote text track object and returns an html track element.
23203     *
23204     * @param {Object} options The object should contain values for
23205     * kind, language, label, and src (location of the WebVTT file)
23206     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
23207     * automatically removed from the video element whenever the source changes
23208     * @return {HTMLTrackElement} An Html Track Element.
23209     * This can be an emulated {@link HTMLTrackElement} or a native one.
23210     * @deprecated The default value of the "manualCleanup" parameter will default
23211     * to "false" in upcoming versions of Video.js
23212     */
23213    ;
23214
23215    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
23216      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
23217
23218      if (this.featuresNativeTextTracks) {
23219        this.el().appendChild(htmlTrackElement);
23220      }
23221
23222      return htmlTrackElement;
23223    }
23224    /**
23225     * Remove remote `TextTrack` from `TextTrackList` object
23226     *
23227     * @param {TextTrack} track
23228     *        `TextTrack` object to remove
23229     */
23230    ;
23231
23232    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
23233      _Tech.prototype.removeRemoteTextTrack.call(this, track);
23234
23235      if (this.featuresNativeTextTracks) {
23236        var tracks = this.$$('track');
23237        var i = tracks.length;
23238
23239        while (i--) {
23240          if (track === tracks[i] || track === tracks[i].track) {
23241            this.el().removeChild(tracks[i]);
23242          }
23243        }
23244      }
23245    }
23246    /**
23247     * Gets available media playback quality metrics as specified by the W3C's Media
23248     * Playback Quality API.
23249     *
23250     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
23251     *
23252     * @return {Object}
23253     *         An object with supported media playback quality metrics
23254     */
23255    ;
23256
23257    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
23258      if (typeof this.el().getVideoPlaybackQuality === 'function') {
23259        return this.el().getVideoPlaybackQuality();
23260      }
23261
23262      var videoPlaybackQuality = {};
23263
23264      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
23265        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
23266        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
23267      }
23268
23269      if (window_1.performance && typeof window_1.performance.now === 'function') {
23270        videoPlaybackQuality.creationTime = window_1.performance.now();
23271      } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') {
23272        videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart;
23273      }
23274
23275      return videoPlaybackQuality;
23276    };
23277
23278    return Html5;
23279  }(Tech);
23280  /* HTML5 Support Testing ---------------------------------------------------- */
23281
23282  /**
23283   * Element for testing browser HTML5 media capabilities
23284   *
23285   * @type {Element}
23286   * @constant
23287   * @private
23288   */
23289
23290
23291  defineLazyProperty(Html5, 'TEST_VID', function () {
23292    if (!isReal()) {
23293      return;
23294    }
23295
23296    var video = document_1.createElement('video');
23297    var track = document_1.createElement('track');
23298    track.kind = 'captions';
23299    track.srclang = 'en';
23300    track.label = 'English';
23301    video.appendChild(track);
23302    return video;
23303  });
23304  /**
23305   * Check if HTML5 media is supported by this browser/device.
23306   *
23307   * @return {boolean}
23308   *         - True if HTML5 media is supported.
23309   *         - False if HTML5 media is not supported.
23310   */
23311
23312  Html5.isSupported = function () {
23313    // IE with no Media Player is a LIAR! (#984)
23314    try {
23315      Html5.TEST_VID.volume = 0.5;
23316    } catch (e) {
23317      return false;
23318    }
23319
23320    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
23321  };
23322  /**
23323   * Check if the tech can support the given type
23324   *
23325   * @param {string} type
23326   *        The mimetype to check
23327   * @return {string} 'probably', 'maybe', or '' (empty string)
23328   */
23329
23330
23331  Html5.canPlayType = function (type) {
23332    return Html5.TEST_VID.canPlayType(type);
23333  };
23334  /**
23335   * Check if the tech can support the given source
23336   *
23337   * @param {Object} srcObj
23338   *        The source object
23339   * @param {Object} options
23340   *        The options passed to the tech
23341   * @return {string} 'probably', 'maybe', or '' (empty string)
23342   */
23343
23344
23345  Html5.canPlaySource = function (srcObj, options) {
23346    return Html5.canPlayType(srcObj.type);
23347  };
23348  /**
23349   * Check if the volume can be changed in this browser/device.
23350   * Volume cannot be changed in a lot of mobile devices.
23351   * Specifically, it can't be changed from 1 on iOS.
23352   *
23353   * @return {boolean}
23354   *         - True if volume can be controlled
23355   *         - False otherwise
23356   */
23357
23358
23359  Html5.canControlVolume = function () {
23360    // IE will error if Windows Media Player not installed #3315
23361    try {
23362      var volume = Html5.TEST_VID.volume;
23363      Html5.TEST_VID.volume = volume / 2 + 0.1;
23364      return volume !== Html5.TEST_VID.volume;
23365    } catch (e) {
23366      return false;
23367    }
23368  };
23369  /**
23370   * Check if the volume can be muted in this browser/device.
23371   * Some devices, e.g. iOS, don't allow changing volume
23372   * but permits muting/unmuting.
23373   *
23374   * @return {bolean}
23375   *      - True if volume can be muted
23376   *      - False otherwise
23377   */
23378
23379
23380  Html5.canMuteVolume = function () {
23381    try {
23382      var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
23383      // work, so we want to set both property and attribute
23384
23385      Html5.TEST_VID.muted = !muted;
23386
23387      if (Html5.TEST_VID.muted) {
23388        setAttribute(Html5.TEST_VID, 'muted', 'muted');
23389      } else {
23390        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
23391      }
23392
23393      return muted !== Html5.TEST_VID.muted;
23394    } catch (e) {
23395      return false;
23396    }
23397  };
23398  /**
23399   * Check if the playback rate can be changed in this browser/device.
23400   *
23401   * @return {boolean}
23402   *         - True if playback rate can be controlled
23403   *         - False otherwise
23404   */
23405
23406
23407  Html5.canControlPlaybackRate = function () {
23408    // Playback rate API is implemented in Android Chrome, but doesn't do anything
23409    // https://github.com/videojs/video.js/issues/3180
23410    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
23411      return false;
23412    } // IE will error if Windows Media Player not installed #3315
23413
23414
23415    try {
23416      var playbackRate = Html5.TEST_VID.playbackRate;
23417      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
23418      return playbackRate !== Html5.TEST_VID.playbackRate;
23419    } catch (e) {
23420      return false;
23421    }
23422  };
23423  /**
23424   * Check if we can override a video/audio elements attributes, with
23425   * Object.defineProperty.
23426   *
23427   * @return {boolean}
23428   *         - True if builtin attributes can be overridden
23429   *         - False otherwise
23430   */
23431
23432
23433  Html5.canOverrideAttributes = function () {
23434    // if we cannot overwrite the src/innerHTML property, there is no support
23435    // iOS 7 safari for instance cannot do this.
23436    try {
23437      var noop = function noop() {};
23438
23439      Object.defineProperty(document_1.createElement('video'), 'src', {
23440        get: noop,
23441        set: noop
23442      });
23443      Object.defineProperty(document_1.createElement('audio'), 'src', {
23444        get: noop,
23445        set: noop
23446      });
23447      Object.defineProperty(document_1.createElement('video'), 'innerHTML', {
23448        get: noop,
23449        set: noop
23450      });
23451      Object.defineProperty(document_1.createElement('audio'), 'innerHTML', {
23452        get: noop,
23453        set: noop
23454      });
23455    } catch (e) {
23456      return false;
23457    }
23458
23459    return true;
23460  };
23461  /**
23462   * Check to see if native `TextTrack`s are supported by this browser/device.
23463   *
23464   * @return {boolean}
23465   *         - True if native `TextTrack`s are supported.
23466   *         - False otherwise
23467   */
23468
23469
23470  Html5.supportsNativeTextTracks = function () {
23471    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
23472  };
23473  /**
23474   * Check to see if native `VideoTrack`s are supported by this browser/device
23475   *
23476   * @return {boolean}
23477   *        - True if native `VideoTrack`s are supported.
23478   *        - False otherwise
23479   */
23480
23481
23482  Html5.supportsNativeVideoTracks = function () {
23483    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
23484  };
23485  /**
23486   * Check to see if native `AudioTrack`s are supported by this browser/device
23487   *
23488   * @return {boolean}
23489   *        - True if native `AudioTrack`s are supported.
23490   *        - False otherwise
23491   */
23492
23493
23494  Html5.supportsNativeAudioTracks = function () {
23495    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
23496  };
23497  /**
23498   * An array of events available on the Html5 tech.
23499   *
23500   * @private
23501   * @type {Array}
23502   */
23503
23504
23505  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'];
23506  /**
23507   * Boolean indicating whether the `Tech` supports volume control.
23508   *
23509   * @type {boolean}
23510   * @default {@link Html5.canControlVolume}
23511   */
23512
23513  /**
23514   * Boolean indicating whether the `Tech` supports muting volume.
23515   *
23516   * @type {bolean}
23517   * @default {@link Html5.canMuteVolume}
23518   */
23519
23520  /**
23521   * Boolean indicating whether the `Tech` supports changing the speed at which the media
23522   * plays. Examples:
23523   *   - Set player to play 2x (twice) as fast
23524   *   - Set player to play 0.5x (half) as fast
23525   *
23526   * @type {boolean}
23527   * @default {@link Html5.canControlPlaybackRate}
23528   */
23529
23530  /**
23531   * Boolean indicating whether the `Tech` supports the `sourceset` event.
23532   *
23533   * @type {boolean}
23534   * @default
23535   */
23536
23537  /**
23538   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
23539   *
23540   * @type {boolean}
23541   * @default {@link Html5.supportsNativeTextTracks}
23542   */
23543
23544  /**
23545   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
23546   *
23547   * @type {boolean}
23548   * @default {@link Html5.supportsNativeVideoTracks}
23549   */
23550
23551  /**
23552   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
23553   *
23554   * @type {boolean}
23555   * @default {@link Html5.supportsNativeAudioTracks}
23556   */
23557
23558  [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
23559    var key = _ref[0],
23560        fn = _ref[1];
23561    defineLazyProperty(Html5.prototype, key, function () {
23562      return Html5[fn]();
23563    }, true);
23564  });
23565  /**
23566   * Boolean indicating whether the `HTML5` tech currently supports the media element
23567   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
23568   * false there. Everywhere else this should be true.
23569   *
23570   * @type {boolean}
23571   * @default
23572   */
23573
23574  Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
23575  //       Is this true?
23576
23577  /**
23578   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
23579   * when going into fullscreen.
23580   *
23581   * @type {boolean}
23582   * @default
23583   */
23584
23585  Html5.prototype.featuresFullscreenResize = true;
23586  /**
23587   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
23588   * If this is false, manual `progress` events will be triggered instead.
23589   *
23590   * @type {boolean}
23591   * @default
23592   */
23593
23594  Html5.prototype.featuresProgressEvents = true;
23595  /**
23596   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
23597   * If this is false, manual `timeupdate` events will be triggered instead.
23598   *
23599   * @default
23600   */
23601
23602  Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- //
23603
23604  var canPlayType;
23605
23606  Html5.patchCanPlayType = function () {
23607    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
23608    // Firefox and Chrome report correctly
23609    if (ANDROID_VERSION >
vendor: 14,852 bytes, lines 23609-24001
23609= 4.0 && !IS_FIREFOX && !IS_CHROME) {
23610      canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
23611
23612      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
23613        var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
23614
23615        if (type && mpegurlRE.test(type)) {
23616          return 'maybe';
23617        }
23618
23619        return canPlayType.call(this, type);
23620      };
23621    }
23622  };
23623
23624  Html5.unpatchCanPlayType = function () {
23625    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
23626
23627    if (canPlayType) {
23628      Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
23629    }
23630
23631    return r;
23632  }; // by default, patch the media element
23633
23634
23635  Html5.patchCanPlayType();
23636
23637  Html5.disposeMediaElement = function (el) {
23638    if (!el) {
23639      return;
23640    }
23641
23642    if (el.parentNode) {
23643      el.parentNode.removeChild(el);
23644    } // remove any child track or source nodes to prevent their loading
23645
23646
23647    while (el.hasChildNodes()) {
23648      el.removeChild(el.firstChild);
23649    } // remove any src reference. not setting `src=''` because that causes a warning
23650    // in firefox
23651
23652
23653    el.removeAttribute('src'); // force the media element to update its loading state by calling load()
23654    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
23655
23656    if (typeof el.load === 'function') {
23657      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
23658      (function () {
23659        try {
23660          el.load();
23661        } catch (e) {// not supported
23662        }
23663      })();
23664    }
23665  };
23666
23667  Html5.resetMediaElement = function (el) {
23668    if (!el) {
23669      return;
23670    }
23671
23672    var sources = el.querySelectorAll('source');
23673    var i = sources.length;
23674
23675    while (i--) {
23676      el.removeChild(sources[i]);
23677    } // remove any src reference.
23678    // not setting `src=''` because that throws an error
23679
23680
23681    el.removeAttribute('src');
23682
23683    if (typeof el.load === 'function') {
23684      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
23685      (function () {
23686        try {
23687          el.load();
23688        } catch (e) {// satisfy linter
23689        }
23690      })();
23691    }
23692  };
23693  /* Native HTML5 element property wrapping ----------------------------------- */
23694  // Wrap native boolean attributes with getters that check both property and attribute
23695  // The list is as followed:
23696  // muted, defaultMuted, autoplay, controls, loop, playsinline
23697
23698
23699  [
23700  /**
23701   * Get the value of `muted` from the media element. `muted` indicates
23702   * that the volume for the media should be set to silent. This does not actually change
23703   * the `volume` attribute.
23704   *
23705   * @method Html5#muted
23706   * @return {boolean}
23707   *         - True if the value of `volume` should be ignored and the audio set to silent.
23708   *         - False if the value of `volume` should be used.
23709   *
23710   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
23711   */
23712  'muted',
23713  /**
23714   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
23715   * whether the media should start muted or not. Only changes the default state of the
23716   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
23717   * current state.
23718   *
23719   * @method Html5#defaultMuted
23720   * @return {boolean}
23721   *         - The value of `defaultMuted` from the media element.
23722   *         - True indicates that the media should start muted.
23723   *         - False indicates that the media should not start muted
23724   *
23725   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
23726   */
23727  'defaultMuted',
23728  /**
23729   * Get the value of `autoplay` from the media element. `autoplay` indicates
23730   * that the media should start to play as soon as the page is ready.
23731   *
23732   * @method Html5#autoplay
23733   * @return {boolean}
23734   *         - The value of `autoplay` from the media element.
23735   *         - True indicates that the media should start as soon as the page loads.
23736   *         - False indicates that the media should not start as soon as the page loads.
23737   *
23738   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
23739   */
23740  'autoplay',
23741  /**
23742   * Get the value of `controls` from the media element. `controls` indicates
23743   * whether the native media controls should be shown or hidden.
23744   *
23745   * @method Html5#controls
23746   * @return {boolean}
23747   *         - The value of `controls` from the media element.
23748   *         - True indicates that native controls should be showing.
23749   *         - False indicates that native controls should be hidden.
23750   *
23751   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
23752   */
23753  'controls',
23754  /**
23755   * Get the value of `loop` from the media element. `loop` indicates
23756   * that the media should return to the start of the media and continue playing once
23757   * it reaches the end.
23758   *
23759   * @method Html5#loop
23760   * @return {boolean}
23761   *         - The value of `loop` from the media element.
23762   *         - True indicates that playback should seek back to start once
23763   *           the end of a media is reached.
23764   *         - False indicates that playback should not loop back to the start when the
23765   *           end of the media is reached.
23766   *
23767   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
23768   */
23769  'loop',
23770  /**
23771   * Get the value of `playsinline` from the media element. `playsinline` indicates
23772   * to the browser that non-fullscreen playback is preferred when fullscreen
23773   * playback is the native default, such as in iOS Safari.
23774   *
23775   * @method Html5#playsinline
23776   * @return {boolean}
23777   *         - The value of `playsinline` from the media element.
23778   *         - True indicates that the media should play inline.
23779   *         - False indicates that the media should not play inline.
23780   *
23781   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
23782   */
23783  'playsinline'].forEach(function (prop) {
23784    Html5.prototype[prop] = function () {
23785      return this.el_[prop] || this.el_.hasAttribute(prop);
23786    };
23787  }); // Wrap native boolean attributes with setters that set both property and attribute
23788  // The list is as followed:
23789  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
23790  // setControls is special-cased above
23791
23792  [
23793  /**
23794   * Set the value of `muted` on the media element. `muted` indicates that the current
23795   * audio level should be silent.
23796   *
23797   * @method Html5#setMuted
23798   * @param {boolean} muted
23799   *        - True if the audio should be set to silent
23800   *        - False otherwise
23801   *
23802   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
23803   */
23804  'muted',
23805  /**
23806   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
23807   * audio level should be silent, but will only effect the muted level on initial playback..
23808   *
23809   * @method Html5.prototype.setDefaultMuted
23810   * @param {boolean} defaultMuted
23811   *        - True if the audio should be set to silent
23812   *        - False otherwise
23813   *
23814   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
23815   */
23816  'defaultMuted',
23817  /**
23818   * Set the value of `autoplay` on the media element. `autoplay` indicates
23819   * that the media should start to play as soon as the page is ready.
23820   *
23821   * @method Html5#setAutoplay
23822   * @param {boolean} autoplay
23823   *         - True indicates that the media should start as soon as the page loads.
23824   *         - False indicates that the media should not start as soon as the page loads.
23825   *
23826   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
23827   */
23828  'autoplay',
23829  /**
23830   * Set the value of `loop` on the media element. `loop` indicates
23831   * that the media should return to the start of the media and continue playing once
23832   * it reaches the end.
23833   *
23834   * @method Html5#setLoop
23835   * @param {boolean} loop
23836   *         - True indicates that playback should seek back to start once
23837   *           the end of a media is reached.
23838   *         - False indicates that playback should not loop back to the start when the
23839   *           end of the media is reached.
23840   *
23841   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
23842   */
23843  'loop',
23844  /**
23845   * Set the value of `playsinline` from the media element. `playsinline` indicates
23846   * to the browser that non-fullscreen playback is preferred when fullscreen
23847   * playback is the native default, such as in iOS Safari.
23848   *
23849   * @method Html5#setPlaysinline
23850   * @param {boolean} playsinline
23851   *         - True indicates that the media should play inline.
23852   *         - False indicates that the media should not play inline.
23853   *
23854   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
23855   */
23856  'playsinline'].forEach(function (prop) {
23857    Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
23858      this.el_[prop] = v;
23859
23860      if (v) {
23861        this.el_.setAttribute(prop, prop);
23862      } else {
23863        this.el_.removeAttribute(prop);
23864      }
23865    };
23866  }); // Wrap native properties with a getter
23867  // The list is as followed
23868  // paused, currentTime, buffered, volume, poster, preload, error, seeking
23869  // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
23870  // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
23871
23872  [
23873  /**
23874   * Get the value of `paused` from the media element. `paused` indicates whether the media element
23875   * is currently paused or not.
23876   *
23877   * @method Html5#paused
23878   * @return {boolean}
23879   *         The value of `paused` from the media element.
23880   *
23881   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
23882   */
23883  'paused',
23884  /**
23885   * Get the value of `currentTime` from the media element. `currentTime` indicates
23886   * the current second that the media is at in playback.
23887   *
23888   * @method Html5#currentTime
23889   * @return {number}
23890   *         The value of `currentTime` from the media element.
23891   *
23892   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
23893   */
23894  'currentTime',
23895  /**
23896   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
23897   * object that represents the parts of the media that are already downloaded and
23898   * available for playback.
23899   *
23900   * @method Html5#buffered
23901   * @return {TimeRange}
23902   *         The value of `buffered` from the media element.
23903   *
23904   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
23905   */
23906  'buffered',
23907  /**
23908   * Get the value of `volume` from the media element. `volume` indicates
23909   * the current playback volume of audio for a media. `volume` will be a value from 0
23910   * (silent) to 1 (loudest and default).
23911   *
23912   * @method Html5#volume
23913   * @return {number}
23914   *         The value of `volume` from the media element. Value will be between 0-1.
23915   *
23916   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
23917   */
23918  'volume',
23919  /**
23920   * Get the value of `poster` from the media element. `poster` indicates
23921   * that the url of an image file that can/will be shown when no media data is available.
23922   *
23923   * @method Html5#poster
23924   * @return {string}
23925   *         The value of `poster` from the media element. Value will be a url to an
23926   *         image.
23927   *
23928   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
23929   */
23930  'poster',
23931  /**
23932   * Get the value of `preload` from the media element. `preload` indicates
23933   * what should download before the media is interacted with. It can have the following
23934   * values:
23935   * - none: nothing should be downloaded
23936   * - metadata: poster and the first few frames of the media may be downloaded to get
23937   *   media dimensions and other metadata
23938   * - auto: allow the media and metadata for the media to be downloaded before
23939   *    interaction
23940   *
23941   * @method Html5#preload
23942   * @return {string}
23943   *         The value of `preload` from the media element. Will be 'none', 'metadata',
23944   *         or 'auto'.
23945   *
23946   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
23947   */
23948  'preload',
23949  /**
23950   * Get the value of the `error` from the media element. `error` indicates any
23951   * MediaError that may have occurred during playback. If error returns null there is no
23952   * current error.
23953   *
23954   * @method Html5#error
23955   * @return {MediaError|null}
23956   *         The value of `error` from the media element. Will be `MediaError` if there
23957   *         is a current error and null otherwise.
23958   *
23959   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
23960   */
23961  'error',
23962  /**
23963   * Get the value of `seeking` from the media element. `seeking` indicates whether the
23964   * media is currently seeking to a new position or not.
23965   *
23966   * @method Html5#seeking
23967   * @return {boolean}
23968   *         - The value of `seeking` from the media element.
23969   *         - True indicates that the media is currently seeking to a new position.
23970   *         - False indicates that the media is not seeking to a new position at this time.
23971   *
23972   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
23973   */
23974  'seeking',
23975  /**
23976   * Get the value of `seekable` from the media element. `seekable` returns a
23977   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
23978   *
23979   * @method Html5#seekable
23980   * @return {TimeRange}
23981   *         The value of `seekable` from the media element. A `TimeRange` object
23982   *         indicating the current ranges of time that can be seeked to.
23983   *
23984   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
23985   */
23986  'seekable',
23987  /**
23988   * Get the value of `ended` from the media element. `ended` indicates whether
23989   * the media has reached the end or not.
23990   *
23991   * @method Html5#ended
23992   * @return {boolean}
23993   *         - The value of `ended` from the media element.
23994   *         - True indicates that the media has ended.
23995   *         - False indicates that the media has not ended.
23996   *
23997   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
23998   */
23999  'ended',
24000  /**
24001   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
24002   * the rate at which the media is currently playing back. Examples:
24003   *   - if playbackRate is set to 2, media will play twice as fast.
24004   *   - if playbackRate is set to 0.5, media will play half as fast.
24005   *
24006   * @method Html5#playbackRate
24007   * @return {number}
24008   *         The value of `playbackRate` from the media element. A number indicating
24009   *         the current playback speed of the media, where 1 is normal speed.
24010   *
24011   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
24012   */
24013  'playbackRate',
24014  /**
24015   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
24016   * the rate at which the media is currently playing back. This value will not indicate the current
24017   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
24018   *
24019   * Examples:
24020   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
24021   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
24022   *
24023   * @method Html5.prototype.defaultPlaybackRate
24024   * @return {number}
24025   *         The value of `defaultPlaybackRate` from the media element. A number indicating
24026   *         the current playback speed of the media, where 1 is normal speed.
24027   *
24028   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
24029   */
24030  'defaultPlaybackRate',
24031  /**
24032   * Get the value of 'disablePictureInPicture' from the video element.
24033   *
24034   * @method Html5#disablePictureInPicture
24035   * @return {boolean} value
24036   *         - The value of `disablePictureInPicture` from the video element.
24037   *         - True indicates that the video can't be played in Picture-In-Picture mode
24038   *         - False indicates that the video can be played in Picture-In-Picture mode
24039   *
24040   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
24041   */
24042  'disablePictureInPicture',
24043  /**
24044   * Get the value of `played` from the media element. `played` returns a `TimeRange`
24045   * object representing points in the media timeline that have been played.
24046   *
24047   * @method Html5#played
24048   * @return {TimeRange}
24049   *         The value of `played` from the media element. A `TimeRange` object indicating
24050   *         the ranges of time that have been played.
24051   *
24052   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
24053   */
24054  'played',
24055  /**
24056   * Get the value of `networkState` from the media element. `networkState` indicates
24057   * the current network state. It returns an enumeration from the following list:
24058   * - 0: NETWORK_EMPTY
24059   * - 1: NETWORK_IDLE
24060   * - 2: NETWORK_LOADING
24061   * - 3: NETWORK_NO_SOURCE
24062   *
24063   * @method Html5#networkState
24064   * @return {number}
24065   *         The value of `networkState` from the media element. This will be a number
24066   *         from the list in the description.
24067   *
24068   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
24069   */
24070  'networkState',
24071  /**
24072   * Get the value of `readyState` from the media element. `readyState` indicates
24073   * the current state of the media element. It returns an enumeration from the
24074   * following list:
24075   * - 0: HAVE_NOTHING
24076   * - 1: HAVE_METADATA
24077   * - 2: HAVE_CURRENT_DATA
24078   * - 3: HAVE_FUTURE_DATA
24079   * - 4: HAVE_ENOUGH_DATA
24080   *
24081   * @method Html5#readyState
24082   * @return {number}
24083   *         The value of `readyState` from the media element. This will be a number
24084   *         from the list in the description.
24085   *
24086   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
24087   */
24088  'readyState',
24089  /**
24090   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
24091   * the current width of the video in css pixels.
24092   *
24093   * @method Html5#videoWidth
24094   * @return {number}
24095   *         The value of `videoWidth` from the video element. This will be a number
24096   *         in css pixels.
24097   *
24098   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
24099   */
24100  'videoWidth',
24101  /**
24102   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
24103   * the current height of the video in css pixels.
24104   *
24105   * @method Html5#videoHeight
24106   * @return {number}
24107   *         The value of `videoHeight` from the video element. This will be a number
24108   *         in css pixels.
24109   *
24110   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
24111   */
24112  'videoHeight',
24113  /**
24114   * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
24115   * to the browser that should sent the cookies along with the requests for the
24116   * different assets/playlists
24117   *
24118   * @method Html5#crossOrigin
24119   * @return {string}
24120   *         - anonymous indicates that the media should not sent cookies.
24121   *         - use-credentials indicates that the media should sent cookies along the requests.
24122   *
24123   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
24124   */
24125  'crossOrigin'].forEach(function (prop) {
24126    Html5.prototype[prop] = function () {
24127      return this.el_[prop];
24128    };
24129  }); // Wrap native properties with a setter in this format:
24130  // set + toTitleCase(name)
24131  // The list is as follows:
24132  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
24133  // setDisablePictureInPicture, setCrossOrigin
24134
24135  [
24136  /**
24137   * Set the value of `volume` on the media element. `volume` indicates the current
24138   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
24139   * so on.
24140   *
24141   * @method Html5#setVolume
24142   * @param {number} percentAsDecimal
24143   *        The volume percent as a decimal. Valid range is from 0-1.
24144   *
24145   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
24146   */
24147  'volume',
24148  /**
24149   * Set the value of `src` on the media element. `src` indicates the current
24150   * {@link Tech~SourceObject} for the media.
24151   *
24152   * @method Html5#setSrc
24153   * @param {Tech~SourceObject} src
24154   *        The source object to set as the current source.
24155   *
24156   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
24157   */
24158  'src',
24159  /**
24160   * Set the value of `poster` on the media element. `poster` is the url to
24161   * an image file that can/will be shown when no media data is available.
24162   *
24163   * @method Html5#setPoster
24164   * @param {string} poster
24165   *        The url to an image that should be used as the `poster` for the media
24166   *        element.
24167   *
24168   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
24169   */
24170  'poster',
24171  /**
24172   * Set the value of `preload` on the media element. `preload` indicates
24173   * what should download before the media is interacted with. It can have the following
24174   * values:
24175   * - none: nothing should be downloaded
24176   * - metadata: poster and the first few frames of the media may be downloaded to get
24177   *   media dimensions and other metadata
24178   * - auto: allow the media and metadata for the media to be downloaded before
24179   *    interaction
24180   *
24181   * @method Html5#setPreload
24182   * @param {string} preload
24183   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
24184   *         or 'auto'.
24185   *
24186   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
24187   */
24188  'preload',
24189  /**
24190   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
24191   * the rate at which the media should play back. Examples:
24192   *   - if playbackRate is set to 2, media will play twice as fast.
24193   *   - if playbackRate is set to 0.5, media will play half as fast.
24194   *
24195   * @method Html5#setPlaybackRate
24196   * @return {number}
24197   *         The value of `playbackRate` from the media element. A number indicating
24198   *         the current playback speed of the media, where 1 is normal speed.
24199   *
24200   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
24201   */
24202  'playbackRate',
24203  /**
24204   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
24205   * the rate at which the media should play back upon initial startup. Changing this value
24206   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
24207   *
24208   * Example Values:
24209   *   - if playbackRate is set to 2, media will play twice as fast.
24210   *   - if playbackRate is set to 0.5, media will play half as fast.
24211   *
24212   * @method Html5.prototype.setDefaultPlaybackRate
24213   * @return {number}
24214   *         The value of `defaultPlaybackRate` from the media element. A number indicating
24215   *         the current playback speed of the media, where 1 is normal speed.
24216   *
24217   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
24218   */
24219  'defaultPlaybackRate',
24220  /**
24221   * Prevents the browser from suggesting a Picture-in-Picture context menu
24222   * or to request Picture-in-Picture automatically in some cases.
24223   *
24224   * @method Html5#setDisablePictureInPicture
24225   * @param {boolean} value
24226   *         The true value will disable Picture-in-Picture mode.
24227   *
24228   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
24229   */
24230  'disablePictureInPicture',
24231  /**
24232   * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
24233   * to the browser that should sent the cookies along with the requests for the
24234   * different assets/playlists
24235   *
24236   * @method Html5#setCrossOrigin
24237   * @param {string} crossOrigin
24238   *         - anonymous indicates that the media should not sent cookies.
24239   *         - use-credentials indicates that the media should sent cookies along the requests.
24240   *
24241   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
24242   */
24243  'crossOrigin'].forEach(function (prop) {
24244    Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
24245      this.el_[prop] = v;
24246    };
24247  }); // wrap native functions with a function
24248  // The list is as follows:
24249  // pause, load, play
24250
24251  [
24252  /**
24253   * A wrapper around the media elements `pause` function. This will call the `HTML5`
24254   * media elements `pause` function.
24255   *
24256   * @method Html5#pause
24257   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
24258   */
24259  'pause',
24260  /**
24261   * A wrapper around the media elements `load` function. This will call the `HTML5`s
24262   * media element `load` function.
24263   *
24264   * @method Html5#load
24265   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
24266   */
24267  'load',
24268  /**
24269   * A wrapper around the media elements `play` function. This will call the `HTML5`s
24270   * media element `play` function.
24271   *
24272   * @method Html5#play
24273   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
24274   */
24275  'play'].forEach(function (prop) {
24276    Html5.prototype[prop] = function () {
24277      return this.el_[prop]();
24278    };
24279  });
24280  Tech.withSourceHandlers(Html5);
24281  /**
24282   * Native source handler for Html5, simply passes the source to the media element.
24283   *
24284   * @property {Tech~SourceObject} source
24285   *        The source object
24286   *
24287   * @property {Html5} tech
24288   *        The instance of the HTML5 tech.
24289   */
24290
24291  Html5.nativeSourceHandler = {};
24292  /**
24293   * Check if the media element can play the given mime type.
24294   *
24295   * @param {string} type
24296   *        The mimetype to check
24297   *
24298   * @return {string}
24299   *         'probably', 'maybe', or '' (empty string)
24300   */
24301
24302  Html5.nativeSourceHandler.canPlayType = function (type) {
24303    // IE without MediaPlayer throws an error (#519)
24304    try {
24305      return Html5.TEST_VID.canPlayType(type);
24306    } catch (e) {
24307      return '';
24308    }
24309  };
24310  /**
24311   * Check if the media element can handle a source natively.
24312   *
24313   * @param {Tech~SourceObject} source
24314   *         The source object
24315   *
24316   * @param {Object} [options]
24317   *         Options to be passed to the tech.
24318   *
24319   * @return {string}
24320   *         'probably', 'maybe', or '' (empty string).
24321   */
24322
24323
24324  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
24325    // If a type was provided we should rely on that
24326    if (source.type) {
24327      return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
24328    } else if (source.src) {
24329      var ext = getFileExtension(source.src);
24330      return Html5.nativeSourceHandler.canPlayType("video/" + ext);
24331    }
24332
24333    return '';
24334  };
24335  /**
24336   * Pass the source to the native media element.
24337   *
24338   * @param {Tech~SourceObject} source
24339   *        The source object
24340   *
24341   * @param {Html5} tech
24342   *        The instance of the Html5 tech
24343   *
24344   * @param {Object} [options]
24345   *        The options to pass to the source
24346   */
24347
24348
24349  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
24350    tech.setSrc(source.src);
24351  };
24352  /**
24353   * A noop for the native dispose function, as cleanup is not needed.
24354   */
24355
24356
24357  Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
24358
24359
24360  Html5.registerSourceHandler(Html5.nativeSourceHandler);
24361  Tech.registerTech('Html5', Html5);
24362
24363  // on the player when they happen
24364
24365  var TECH_EVENTS_RETRIGGER = [
24366  /**
24367   * Fired while the user agent is downloading media data.
24368   *
24369   * @event Player#progress
24370   * @type {EventTarget~Event}
24371   */
24372
24373  /**
24374   * Retrigger the `progress` event that was triggered by the {@link Tech}.
24375   *
24376   * @private
24377   * @method Player#handleTechProgress_
24378   * @fires Player#progress
24379   * @listens Tech#progress
24380   */
24381  'progress',
24382  /**
24383   * Fires when the loading of an audio/video is aborted.
24384   *
24385   * @event Player#abort
24386   * @type {EventTarget~Event}
24387   */
24388
24389  /**
24390   * Retrigger the `abort` event that was triggered by the {@link Tech}.
24391   *
24392   * @private
24393   * @method Player#handleTechAbort_
24394   * @fires Player#abort
24395   * @listens Tech#abort
24396   */
24397  'abort',
24398  /**
24399   * Fires when the browser is intentionally not getting media data.
24400   *
24401   * @event Player#suspend
24402   * @type {EventTarget~Event}
24403   */
24404
24405  /**
24406   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
24407   *
24408   * @private
24409   * @method Player#handleTechSuspend_
24410   * @fires Player#suspend
24411   * @listens Tech#suspend
24412   */
24413  'suspend',
24414  /**
24415   * Fires when the current playlist is empty.
24416   *
24417   * @event Player#emptied
24418   * @type {EventTarget~Event}
24419   */
24420
24421  /**
24422   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
24423   *
24424   * @private
24425   * @method Player#handleTechEmptied_
24426   * @fires Player#emptied
24427   * @listens Tech#emptied
24428   */
24429  'emptied',
24430  /**
24431   * Fires when the browser is trying to get media data, but data is not available.
24432   *
24433   * @event Player#stalled
24434   * @type {EventTarget~Event}
24435   */
24436
24437  /**
24438   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
24439   *
24440   * @private
24441   * @method Player#handleTechStalled_
24442   * @fires Player#stalled
24443   * @listens Tech#stalled
24444   */
24445  'stalled',
24446  /**
24447   * Fires when the browser has loaded meta data for the audio/video.
24448   *
24449   * @event Player#loadedmetadata
24450   * @type {EventTarget~Event}
24451   */
24452
24453  /**
24454   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
24455   *
24456   * @private
24457   * @method Player#handleTechLoadedmetadata_
24458   * @fires Player#loadedmetadata
24459   * @listens Tech#loadedmetadata
24460   */
24461  'loadedmetadata',
24462  /**
24463   * Fires when the browser has loaded the current frame of the audio/video.
24464   *
24465   * @event Player#loadeddata
24466   * @type {event}
24467   */
24468
24469  /**
24470   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
24471   *
24472   * @private
24473   * @method Player#handleTechLoaddeddata_
24474   * @fires Player#loadeddata
24475   * @listens Tech#loadeddata
24476   */
24477  'loadeddata',
24478  /**
24479   * Fires when the current playback position has changed.
24480   *
24481   * @event Player#timeupdate
24482   * @type {event}
24483   */
24484
24485  /**
24486   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
24487   *
24488   * @private
24489   * @method Player#handleTechTimeUpdate_
24490   * @fires Player#timeupdate
24491   * @listens Tech#timeupdate
24492   */
24493  'timeupdate',
24494  /**
24495   * Fires when the video's intrinsic dimensions change
24496   *
24497   * @event Player#resize
24498   * @type {event}
24499   */
24500
24501  /**
24502   * Retrigger the `resize` event that was triggered by the {@link Tech}.
24503   *
24504   * @private
24505   * @method Player#handleTechResize_
24506   * @fires Player#resize
24507   * @listens Tech#resize
24508   */
24509  'resize',
24510  /**
24511   * Fires when the volume has been changed
24512   *
24513   * @event Player#volumechange
24514   * @type {event}
24515   */
24516
24517  /**
24518   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
24519   *
24520   * @private
24521   * @method Player#handleTechVolumechange_
24522   * @fires Player#volumechange
24523   * @listens Tech#volumechange
24524   */
24525  'volumechange',
24526  /**
24527   * Fires when the text track has been changed
24528   *
24529   * @event Player#texttrackchange
24530   * @type {event}
24531   */
24532
24533  /**
24534   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
24535   *
24536   * @private
24537   * @method Player#handleTechTexttrackchange_
24538   * @fires Player#texttrackchange
24539   * @listens Tech#texttrackchange
24540   */
24541  'texttrackchange']; // events to queue when playback rate is zero
24542  // this is a hash for the sole purpose of mapping non-camel-cased event names
24543  // to camel-cased function names
24544
24545  var TECH_EVENTS_QUEUE = {
24546    canplay: 'CanPlay',
24547    canplaythrough: 'CanPlayThrough',
24548    playing: 'Playing',
24549    seeked: 'Seeked'
24550  };
24551  var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
24552  var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
24553  // grep: vjs-layout-x-small
24554  // grep: vjs-layout-small
24555  // grep: vjs-layout-medium
24556  // grep: vjs-layout-large
24557  // grep: vjs-layout-x-large
24558  // grep: vjs-layout-huge
24559
24560  BREAKPOINT_ORDER.forEach(function (k) {
24561    var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
24562    BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
24563  });
24564  var DEFAULT_BREAKPOINTS = {
24565    tiny: 210,
24566    xsmall: 320,
24567    small: 425,
24568    medium: 768,
24569    large: 1440,
24570    xlarge: 2560,
24571    huge: Infinity
24572  };
24573  /**
24574   * An instance of the `Player` class is created when any of the Video.js setup methods
24575   * are used to initialize a video.
24576   *
24577   * After an instance has been created it can be accessed globally in two ways:
24578   * 1. By calling `videojs('example_video_1');`
24579   * 2. By using it directly via  `videojs.players.example_video_1;`
24580   *
24581   * @extends Component
24582   */
24583
24584  var Player = /*#__PURE__*/function (_Component) {
24585    inheritsLoose(Player, _Component);
24586
24587    /**
24588     * Create an instance of this class.
24589     *
24590     * @param {Element} tag
24591     *        The original video DOM element used for configuring options.
24592     *
24593     * @param {Object} [options]
24594     *        Object of option names and values.
24595     *
24596     * @param {Component~ReadyCallback} [ready]
24597     *        Ready callback function.
24598     */
24599    function Player(tag, options, ready) {
24600      var _this;
24601
24602      // Make sure tag ID exists
24603      tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
24604      // The options argument overrides options set in the video tag
24605      // which overrides globally set options.
24606      // This latter part coincides with the load order
24607      // (tag must exist before Player)
24608
24609      options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
24610      // player properties first, and can't use `this` before `super()`
24611
24612      options.initChildren = false; // Same with creating the element
24613
24614      options.createEl = false; // don't auto mixin the evented mixin
24615
24616      options.evented = false; // we don't want the player to report touch activity on itself
24617      // see enableTouchActivity in Component
24618
24619      options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
24620
24621      if (!options.language) {
24622        if (typeof tag.closest === 'function') {
24623          var closest = tag.closest('[lang]');
24624
24625          if (closest && closest.getAttribute) {
24626            options.language = closest.getAttribute('lang');
24627          }
24628        } else {
24629          var element = tag;
24630
24631          while (element && element.nodeType === 1) {
24632            if (getAttributes(element).hasOwnProperty('lang')) {
24633              options.language = element.getAttribute('lang');
24634              break;
24635            }
24636
24637            element = element.parentNode;
24638          }
24639        }
24640      } // Run base component initializing with new options
24641
24642
24643      _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
24644
24645      _this.boundDocumentFullscreenChange_ = function (e) {
24646        return _this.documentFullscreenChange_(e);
24647      };
24648
24649      _this.boundFullWindowOnEscKey_ = function (e) {
24650        return _this.fullWindowOnEscKey(e);
24651      };
24652
24653      _this.boundUpdateStyleEl_ = function (e) {
24654        return _this.updateStyleEl_(e);
24655      };
24656
24657      _this.boundApplyInitTime_ = function (e) {
24658        return _this.applyInitTime_(e);
24659      };
24660
24661      _this.boundUpdateCurrentBreakpoint_ = function (e) {
24662        return _this.updateCurrentBreakpoint_(e);
24663      };
24664
24665      _this.boundHandleTechClick_ = function (e) {
24666        return _this.handleTechClick_(e);
24667      };
24668
24669      _this.boundHandleTechDoubleClick_ = function (e) {
24670        return _this.handleTechDoubleClick_(e);
24671      };
24672
24673      _this.boundHandleTechTouchStart_ = function (e) {
24674        return _this.handleTechTouchStart_(e);
24675      };
24676
24677      _this.boundHandleTechTouchMove_ = function (e) {
24678        return _this.handleTechTouchMove_(e);
24679      };
24680
24681      _this.boundHandleTechTouchEnd_ = function (e) {
24682        return _this.handleTechTouchEnd_(e);
24683      };
24684
24685      _this.boundHandleTechTap_ = function (e) {
24686        return _this.handleTechTap_(e);
24687      }; // default isFullscreen_ to false
24688
24689
24690      _this.isFullscreen_ = false; // create logger
24691
24692      _this.log = createLogger(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests
24693
24694      _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster
24695
24696      _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
24697      // and a seek is happening
24698
24699      _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
24700
24701      _this.isReady_ = false; // Init state hasStarted_
24702
24703      _this.hasStarted_ = false; // Init state userActive_
24704
24705      _this.userActive_ = false; // Init debugEnabled_
24706
24707      _this.debugEnabled_ = false; // if the global option object was accidentally blown away by
24708      // someone, bail early with an informative error
24709
24710      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
24711        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
24712      } // Store the original tag used to set options
24713
24714
24715      _this.tag = tag; // Store the tag attributes used to restore html5 element
24716
24717      _this.tagAttributes = tag && getAttributes(tag); // Update current language
24718
24719      _this.language(_this.options_.language); // Update Supported Languages
24720
24721
24722      if (options.languages) {
24723        // Normalise player option languages to lowercase
24724        var languagesToLower = {};
24725        Object.getOwnPropertyNames(options.languages).forEach(function (name) {
24726          languagesToLower[name.toLowerCase()] = options.languages[name];
24727        });
24728        _this.languages_ = languagesToLower;
24729      } else {
24730        _this.languages_ = Player.prototype.options_.languages;
24731      }
24732
24733      _this.resetCache_(); // Set poster
24734
24735
24736      _this.poster_ = options.poster || ''; // Set controls
24737
24738      _this.controls_ = !!options.controls; // Original tag settings stored in options
24739      // now remove immediately so native controls don't flash.
24740      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
24741
24742      tag.controls = false;
24743      tag.removeAttribute('controls');
24744      _this.changingSrc_ = false;
24745      _this.playCallbacks_ = [];
24746      _this.playTerminatedQueue_ = []; // the attribute overrides the option
24747
24748      if (tag.hasAttribute('autoplay')) {
24749        _this.autoplay(true);
24750      } else {
24751        // otherwise use the setter to validate and
24752        // set the correct value.
24753        _this.autoplay(_this.options_.autoplay);
24754      } // check plugins
24755
24756
24757      if (options.plugins) {
24758        Object.keys(options.plugins).forEach(function (name) {
24759          if (typeof _this[name] !== 'function') {
24760            throw new Error("plugin \"" + name + "\" does not exist");
24761          }
24762        });
24763      }
24764      /*
24765       * Store the internal state of scrubbing
24766       *
24767       * @private
24768       * @return {Boolean} True if the user is scrubbing
24769       */
24770
24771
24772      _this.scrubbing_ = false;
24773      _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
24774
24775      evented(assertThisInitialized(_this), {
24776        eventBusKey: 'el_'
24777      }); // listen to document and player fullscreenchange handlers so we receive those events
24778      // before a user can receive them so we can update isFullscreen appropriately.
24779      // make sure that we listen to fullscreenchange events before everything else to make sure that
24780      // our isFullscreen method is updated properly for internal components as well as external.
24781
24782      if (_this.fsApi_.requestFullscreen) {
24783        on(document_1, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
24784
24785        _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
24786      }
24787
24788      if (_this.fluid_) {
24789        _this.on(['playerreset', 'resize'], _this.boundUpdateStyleEl_);
24790      } // We also want to pass the original player options to each component and plugin
24791      // as well so they don't need to reach back into the player for options later.
24792      // We also need to do another copy of this.options_ so we don't end up with
24793      // an infinite loop.
24794
24795
24796      var playerOptionsCopy = mergeOptions$3(_this.options_); // Load plugins
24797
24798      if (options.plugins) {
24799        Object.keys(options.plugins).forEach(function (name) {
24800          _this[name](options.plugins[name]);
24801        });
24802      } // Enable debug mode to fire debugon event for all plugins.
24803
24804
24805      if (options.debug) {
24806        _this.debug(true);
24807      }
24808
24809      _this.options_.playerOptions = playerOptionsCopy;
24810      _this.middleware_ = [];
24811
24812      _this.playbackRates(options.playbackRates);
24813
24814      _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
24815
24816
24817      _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
24818      // set because the element doesn't exist yet.
24819
24820
24821      if (_this.controls()) {
24822        _this.addClass('vjs-controls-enabled');
24823      } else {
24824        _this.addClass('vjs-controls-disabled');
24825      } // Set ARIA label and region role depending on player type
24826
24827
24828      _this.el_.setAttribute('role', 'region');
24829
24830      if (_this.isAudio()) {
24831        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
24832      } else {
24833        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
24834      }
24835
24836      if (_this.isAudio()) {
24837        _this.addClass('vjs-audio');
24838      }
24839
24840      if (_this.flexNotSupported_()) {
24841        _this.addClass('vjs-no-flex');
24842      } // TODO: Make this smarter. Toggle user state between touching/mousing
24843      // using events, since devices can have both touch and mouse events.
24844      // TODO: Make this check be performed again when the window switches between monitors
24845      // (See https://github.com/videojs/video.js/issues/5683)
24846
24847
24848      if (TOUCH_ENABLED) {
24849        _this.addClass('vjs-touch-enabled');
24850      } // iOS Safari has broken hover handling
24851
24852
24853      if (!IS_IOS) {
24854        _this.addClass('vjs-workinghover');
24855      } // Make player easily findable by ID
24856
24857
24858      Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins
24859
24860      var majorVersion = version$5.split('.')[0];
24861
24862      _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
24863      // like the control bar show themselves if needed
24864
24865
24866      _this.userActive(true);
24867
24868      _this.reportUserActivity();
24869
24870      _this.one('play', function (e) {
24871        return _this.listenForUserActivity_(e);
24872      });
24873
24874      _this.on('stageclick', function (e) {
24875        return _this.handleStageClick_(e);
24876      });
24877
24878      _this.on('keydown', function (e) {
24879        return _this.handleKeyDown(e);
24880      });
24881
24882      _this.on('languagechange', function (e) {
24883        return _this.handleLanguagechange(e);
24884      });
24885
24886      _this.breakpoints(_this.options_.breakpoints);
24887
24888      _this.responsive(_this.options_.responsive);
24889
24890      return _this;
24891    }
24892    /**
24893     * Destroys the video player and does any necessary cleanup.
24894     *
24895     * This is especially helpful if you are dynamically adding and removing videos
24896     * to/from the DOM.
24897     *
24898     * @fires Player#dispose
24899     */
24900
24901
24902    var _proto = Player.prototype;
24903
24904    _proto.dispose = function dispose() {
24905      var _this2 = this;
24906
24907      /**
24908       * Called when the player is being disposed of.
24909       *
24910       * @event Player#dispose
24911       * @type {EventTarget~Event}
24912       */
24913      this.trigger('dispose'); // prevent dispose from being called twice
24914
24915      this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
24916
24917      off(document_1, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
24918      off(document_1, 'keydown', this.boundFullWindowOnEscKey_);
24919
24920      if (this.styleEl_ && this.styleEl_.parentNode) {
24921        this.styleEl_.parentNode.removeChild(this.styleEl_);
24922        this.styleEl_ = null;
24923      } // Kill reference to this player
24924
24925
24926      Player.players[this.id_] = null;
24927
24928      if (this.tag && this.tag.player) {
24929        this.tag.player = null;
24930      }
24931
24932      if (this.el_ && this.el_.player) {
24933        this.el_.player = null;
24934      }
24935
24936      if (this.tech_) {
24937        this.tech_.dispose();
24938        this.isPosterFromTech_ = false;
24939        this.poster_ = '';
24940      }
24941
24942      if (this.playerElIngest_) {
24943        this.playerElIngest_ = null;
24944      }
24945
24946      if (this.tag) {
24947        this.tag = null;
24948      }
24949
24950      clearCacheForPlayer(this); // remove all event handlers for track lists
24951      // all tracks and track listeners are removed on
24952      // tech dispose
24953
24954      ALL.names.forEach(function (name) {
24955        var props = ALL[name];
24956
24957        var list = _this2[props.getterName](); // if it is not a native list
24958        // we have to manually remove event listeners
24959
24960
24961        if (list && list.off) {
24962          list.off();
24963        }
24964      }); // the actual .el_ is removed here
24965
24966      _Component.prototype.dispose.call(this);
24967    }
24968    /**
24969     * Create the `Player`'s DOM element.
24970     *
24971     * @return {Element}
24972     *         The DOM element that gets created.
24973     */
24974    ;
24975
24976    _proto.createEl = function createEl() {
24977      var tag = this.tag;
24978      var el;
24979      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
24980      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
24981
24982      if (playerElIngest) {
24983        el = this.el_ = tag.parentNode;
24984      } else if (!divEmbed) {
24985        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
24986      } // Copy over all the attributes from the tag, including ID and class
24987      // ID will now reference player box, not the video tag
24988
24989
24990      var attrs = getAttributes(tag);
24991
24992      if (divEmbed) {
24993        el = this.el_ = tag;
24994        tag = this.tag = document_1.createElement('video');
24995
24996        while (el.children.length) {
24997          tag.appendChild(el.firstChild);
24998        }
24999
25000        if (!hasClass(el, 'video-js')) {
25001          addClass(el, 'video-js');
25002        }
25003
25004        el.appendChild(tag);
25005        playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
25006        // to our new `video` element. This will move things like
25007        // `src` or `controls` that were set via js before the player
25008        // was initialized.
25009
25010        Object.keys(el).forEach(function (k) {
25011          try {
25012            tag[k] = el[k];
25013          } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it
25014          }
25015        });
25016      } // set tabindex to -1 to remove the video element from the focus order
25017
25018
25019      tag.setAttribute('tabindex', '-1');
25020      attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
25021      // for the same issue with Chrome (on Windows) with JAWS.
25022      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
25023      // Note that we can't detect if JAWS is being used, but this ARIA attribute
25024      //  doesn't change behavior of IE11 or Chrome if JAWS is not being used
25025
25026      if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
25027        tag.setAttribute('role', 'application');
25028        attrs.role = 'application';
25029      } // Remove width/height attrs from tag so CSS can make it 100% width/height
25030
25031
25032      tag.removeAttribute('width');
25033      tag.removeAttribute('height');
25034
25035      if ('width' in attrs) {
25036        delete attrs.width;
25037      }
25038
25039      if ('height' in attrs) {
25040        delete attrs.height;
25041      }
25042
25043      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
25044        // don't copy over the class attribute to the player element when we're in a div embed
25045        // the class is already set up properly in the divEmbed case
25046        // and we want to make sure that the `video-js` class doesn't get lost
25047        if (!(divEmbed && attr === 'class')) {
25048          el.setAttribute(attr, attrs[attr]);
25049        }
25050
25051        if (divEmbed) {
25052          tag.setAttribute(attr, attrs[attr]);
25053        }
25054      }); // Update tag id/class for use as HTML5 playback tech
25055      // Might think we should do this after embedding in container so .vjs-tech class
25056      // doesn't flash 100% width/height, but class only applies with .video-js parent
25057
25058      tag.playerId = tag.id;
25059      tag.id += '_html5_api';
25060      tag.className = 'vjs-tech'; // Make player findable on elements
25061
25062      tag.player = el.player = this; // Default state of video is paused
25063
25064      this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
25065      // of the player in a way that's still overrideable by CSS, just like the
25066      // video element
25067
25068      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
25069        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
25070        var defaultsStyleEl = $('.vjs-styles-defaults');
25071        var head = $('head');
25072        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
25073      }
25074
25075      this.fill_ = false;
25076      this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
25077
25078      this.width(this.options_.width);
25079      this.height(this.options_.height);
25080      this.fill(this.options_.fill);
25081      this.fluid(this.options_.fluid);
25082      this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name
25083
25084      this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag,
25085      // because IE doesn't hide them completely from screen readers.
25086
25087      var links = tag.getElementsByTagName('a');
25088
25089      for (var i = 0; i < links.length; i++) {
25090        var linkEl = links.item(i);
25091        addClass(linkEl, 'vjs-hidden');
25092        linkEl.setAttribute('hidden', 'hidden');
25093      } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
25094      // keep track of the original for later so we can know if the source originally failed
25095
25096
25097      tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
25098
25099      if (tag.parentNode && !playerElIngest) {
25100        tag.parentNode.insertBefore(el, tag);
25101      } // insert the tag as the first child of the player element
25102      // then manually add it to the children array so that this.addChild
25103      // will work properly for other components
25104      //
25105      // Breaks iPhone, fixed in HTML5 setup.
25106
25107
25108      prependTo(tag, el);
25109      this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
25110      // if it's been set to something different to the doc
25111
25112      this.el_.setAttribute('lang', this.language_);
25113      this.el_ = el;
25114      return el;
25115    }
25116    /**
25117     * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
25118     * sets the `crossOrigin` property on the `<video>` tag to control the CORS
25119     * behavior.
25120     *
25121     * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
25122     *
25123     * @param {string} [value]
25124     *        The value to set the `Player`'s crossOrigin to. If an argument is
25125     *        given, must be one of `anonymous` or `use-credentials`.
25126     *
25127     * @return {string|undefined}
25128     *         - The current crossOrigin value of the `Player` when getting.
25129     *         - undefined when setting
25130     */
25131    ;
25132
25133    _proto.crossOrigin = function crossOrigin(value) {
25134      if (!value) {
25135        return this.techGet_('crossOrigin');
25136      }
25137
25138      if (value !== 'anonymous' && value !== 'use-credentials') {
25139        log$1.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\"");
25140        return;
25141      }
25142
25143      this.techCall_('setCrossOrigin', value);
25144      return;
25145    }
25146    /**
25147     * A getter/setter for the `Player`'s width. Returns the player'
25147s configured value.
25148     * To get the current width use `currentWidth()`.
25149     *
25150     * @param {number} [value]
25151     *        The value to set the `Player`'s width to.
25152     *
25153     * @return {number}
25154     *         The current width of the `Player` when getting.
25155     */
25156    ;
25157
25158    _proto.width = function width(value) {
25159      return this.dimension('width', value);
25160    }
25161    /**
25162     * A getter/setter for the `Player`'s height. Returns the player's configured value.
25163     * To get the current height use `currentheight()`.
25164     *
25165     * @param {number} [value]
25166     *        The value to set the `Player`'s heigth to.
25167     *
25168     * @return {number}
25169     *         The current height of the `Player` when getting.
25170     */
25171    ;
25172
25173    _proto.height = function height(value) {
25174      return this.dimension('height', value);
25175    }
25176    /**
25177     * A getter/setter for the `Player`'s width & height.
25178     *
25179     * @param {string} dimension
25180     *        This string can be:
25181     *        - 'width'
25182     *        - 'height'
25183     *
25184     * @param {number} [value]
25185     *        Value for dimension specified in the first argument.
25186     *
25187     * @return {number}
25188     *         The dimension arguments value when getting (width/height).
25189     */
25190    ;
25191
25192    _proto.dimension = function dimension(_dimension, value) {
25193      var privDimension = _dimension + '_';
25194
25195      if (value === undefined) {
25196        return this[privDimension] || 0;
25197      }
25198
25199      if (value === '' || value === 'auto') {
25200        // If an empty string is given, reset the dimension to be automatic
25201        this[privDimension] = undefined;
25202        this.updateStyleEl_();
25203        return;
25204      }
25205
25206      var parsedVal = parseFloat(value);
25207
25208      if (isNaN(parsedVal)) {
25209        log$1.error("Improper value \"" + value + "\" supplied for for " + _dimension);
25210        return;
25211      }
25212
25213      this[privDimension] = parsedVal;
25214      this.updateStyleEl_();
25215    }
25216    /**
25217     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
25218     *
25219     * Turning this on will turn off fill mode.
25220     *
25221     * @param {boolean} [bool]
25222     *        - A value of true adds the class.
25223     *        - A value of false removes the class.
25224     *        - No value will be a getter.
25225     *
25226     * @return {boolean|undefined}
25227     *         - The value of fluid when getting.
25228     *         - `undefined` when setting.
25229     */
25230    ;
25231
25232    _proto.fluid = function fluid(bool) {
25233      var _this3 = this;
25234
25235      if (bool === undefined) {
25236        return !!this.fluid_;
25237      }
25238
25239      this.fluid_ = !!bool;
25240
25241      if (isEvented(this)) {
25242        this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
25243      }
25244
25245      if (bool) {
25246        this.addClass('vjs-fluid');
25247        this.fill(false);
25248        addEventedCallback(this, function () {
25249          _this3.on(['playerreset', 'resize'], _this3.boundUpdateStyleEl_);
25250        });
25251      } else {
25252        this.removeClass('vjs-fluid');
25253      }
25254
25255      this.updateStyleEl_();
25256    }
25257    /**
25258     * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
25259     *
25260     * Turning this on will turn off fluid mode.
25261     *
25262     * @param {boolean} [bool]
25263     *        - A value of true adds the class.
25264     *        - A value of false removes the class.
25265     *        - No value will be a getter.
25266     *
25267     * @return {boolean|undefined}
25268     *         - The value of fluid when getting.
25269     *         - `undefined` when setting.
25270     */
25271    ;
25272
25273    _proto.fill = function fill(bool) {
25274      if (bool === undefined) {
25275        return !!this.fill_;
25276      }
25277
25278      this.fill_ = !!bool;
25279
25280      if (bool) {
25281        this.addClass('vjs-fill');
25282        this.fluid(false);
25283      } else {
25284        this.removeClass('vjs-fill');
25285      }
25286    }
25287    /**
25288     * Get/Set the aspect ratio
25289     *
25290     * @param {string} [ratio]
25291     *        Aspect ratio for player
25292     *
25293     * @return {string|undefined}
25294     *         returns the current aspect ratio when getting
25295     */
25296
25297    /**
25298     * A getter/setter for the `Player`'s aspect ratio.
25299     *
25300     * @param {string} [ratio]
25301     *        The value to set the `Player`'s aspect ratio to.
25302     *
25303     * @return {string|undefined}
25304     *         - The current aspect ratio of the `Player` when getting.
25305     *         - undefined when setting
25306     */
25307    ;
25308
25309    _proto.aspectRatio = function aspectRatio(ratio) {
25310      if (ratio === undefined) {
25311        return this.aspectRatio_;
25312      } // Check for width:height format
25313
25314
25315      if (!/^\d+\:\d+$/.test(ratio)) {
25316        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
25317      }
25318
25319      this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
25320      // because in fixed mode you could calculate width and height yourself.
25321
25322      this.fluid(true);
25323      this.updateStyleEl_();
25324    }
25325    /**
25326     * Update styles of the `Player` element (height, width and aspect ratio).
25327     *
25328     * @private
25329     * @listens Tech#loadedmetadata
25330     */
25331    ;
25332
25333    _proto.updateStyleEl_ = function updateStyleEl_() {
25334      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
25335        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
25336
25337        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
25338
25339        var techEl = this.tech_ && this.tech_.el();
25340
25341        if (techEl) {
25342          if (_width >= 0) {
25343            techEl.width = _width;
25344          }
25345
25346          if (_height >= 0) {
25347            techEl.height = _height;
25348          }
25349        }
25350
25351        return;
25352      }
25353
25354      var width;
25355      var height;
25356      var aspectRatio;
25357      var idClass; // The aspect ratio is either used directly or to calculate width and height.
25358
25359      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
25360        // Use any aspectRatio that's been specifically set
25361        aspectRatio = this.aspectRatio_;
25362      } else if (this.videoWidth() > 0) {
25363        // Otherwise try to get the aspect ratio from the video metadata
25364        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
25365      } else {
25366        // Or use a default. The video element's is 2:1, but 16:9 is more common.
25367        aspectRatio = '16:9';
25368      } // Get the ratio as a decimal we can use to calculate dimensions
25369
25370
25371      var ratioParts = aspectRatio.split(':');
25372      var ratioMultiplier = ratioParts[1] / ratioParts[0];
25373
25374      if (this.width_ !== undefined) {
25375        // Use any width that's been specifically set
25376        width = this.width_;
25377      } else if (this.height_ !== undefined) {
25378        // Or calulate the width from the aspect ratio if a height has been set
25379        width = this.height_ / ratioMultiplier;
25380      } else {
25381        // Or use the video's metadata, or use the video el's default of 300
25382        width = this.videoWidth() || 300;
25383      }
25384
25385      if (this.height_ !== undefined) {
25386        // Use any height that's been specifically set
25387        height = this.height_;
25388      } else {
25389        // Otherwise calculate the height from the ratio and the width
25390        height = width * ratioMultiplier;
25391      } // Ensure the CSS class is valid by starting with an alpha character
25392
25393
25394      if (/^[^a-zA-Z]/.test(this.id())) {
25395        idClass = 'dimensions-' + this.id();
25396      } else {
25397        idClass = this.id() + '-dimensions';
25398      } // Ensure the right class is still on the player for the style element
25399
25400
25401      this.addClass(idClass);
25402      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    ");
25403    }
25404    /**
25405     * Load/Create an instance of playback {@link Tech} including element
25406     * and API methods. Then append the `Tech` element in `Player` as a child.
25407     *
25408     * @param {string} techName
25409     *        name of the playback technology
25410     *
25411     * @param {string} source
25412     *        video source
25413     *
25414     * @private
25415     */
25416    ;
25417
25418    _proto.loadTech_ = function loadTech_(techName, source) {
25419      var _this4 = this;
25420
25421      // Pause and remove current playback technology
25422      if (this.tech_) {
25423        this.unloadTech_();
25424      }
25425
25426      var titleTechName = toTitleCase$1(techName);
25427      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
25428
25429      if (titleTechName !== 'Html5' && this.tag) {
25430        Tech.getTech('Html5').disposeMediaElement(this.tag);
25431        this.tag.player = null;
25432        this.tag = null;
25433      }
25434
25435      this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
25436
25437      this.isReady_ = false;
25438      var autoplay = this.autoplay(); // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
25439      // because the player is going to handle autoplay on `loadstart`
25440
25441      if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
25442        autoplay = false;
25443      } // Grab tech-specific options from player options and add source and parent element to use.
25444
25445
25446      var techOptions = {
25447        source: source,
25448        autoplay: autoplay,
25449        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
25450        'playerId': this.id(),
25451        'techId': this.id() + "_" + camelTechName + "_api",
25452        'playsinline': this.options_.playsinline,
25453        'preload': this.options_.preload,
25454        'loop': this.options_.loop,
25455        'disablePictureInPicture': this.options_.disablePictureInPicture,
25456        'muted': this.options_.muted,
25457        'poster': this.poster(),
25458        'language': this.language(),
25459        'playerElIngest': this.playerElIngest_ || false,
25460        'vtt.js': this.options_['vtt.js'],
25461        'canOverridePoster': !!this.options_.techCanOverridePoster,
25462        'enableSourceset': this.options_.enableSourceset,
25463        'Promise': this.options_.Promise
25464      };
25465      ALL.names.forEach(function (name) {
25466        var props = ALL[name];
25467        techOptions[props.getterName] = _this4[props.privateName];
25468      });
25469      assign(techOptions, this.options_[titleTechName]);
25470      assign(techOptions, this.options_[camelTechName]);
25471      assign(techOptions, this.options_[techName.toLowerCase()]);
25472
25473      if (this.tag) {
25474        techOptions.tag = this.tag;
25475      }
25476
25477      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
25478        techOptions.startTime = this.cache_.currentTime;
25479      } // Initialize tech instance
25480
25481
25482      var TechClass = Tech.getTech(techName);
25483
25484      if (!TechClass) {
25485        throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
25486      }
25487
25488      this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
25489
25490      this.tech_.ready(bind(this, this.handleTechReady_), true);
25491      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
25492
25493      TECH_EVENTS_RETRIGGER.forEach(function (event) {
25494        _this4.on(_this4.tech_, event, function (e) {
25495          return _this4["handleTech" + toTitleCase$1(event) + "_"](e);
25496        });
25497      });
25498      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
25499        _this4.on(_this4.tech_, event, function (eventObj) {
25500          if (_this4.tech_.playbackRate() === 0 && _this4.tech_.seeking()) {
25501            _this4.queuedCallbacks_.push({
25502              callback: _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this4),
25503              event: eventObj
25504            });
25505
25506            return;
25507          }
25508
25509          _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
25510        });
25511      });
25512      this.on(this.tech_, 'loadstart', function (e) {
25513        return _this4.handleTechLoadStart_(e);
25514      });
25515      this.on(this.tech_, 'sourceset', function (e) {
25516        return _this4.handleTechSourceset_(e);
25517      });
25518      this.on(this.tech_, 'waiting', function (e) {
25519        return _this4.handleTechWaiting_(e);
25520      });
25521      this.on(this.tech_, 'ended', function (e) {
25522        return _this4.handleTechEnded_(e);
25523      });
25524      this.on(this.tech_, 'seeking', function (e) {
25525        return _this4.handleTechSeeking_(e);
25526      });
25527      this.on(this.tech_, 'play', function (e) {
25528        return _this4.handleTechPlay_(e);
25529      });
25530      this.on(this.tech_, 'firstplay', function (e) {
25531        return _this4.handleTechFirstPlay_(e);
25532      });
25533      this.on(this.tech_, 'pause', function (e) {
25534        return _this4.handleTechPause_(e);
25535      });
25536      this.on(this.tech_, 'durationchange', function (e) {
25537        return _this4.handleTechDurationChange_(e);
25538      });
25539      this.on(this.tech_, 'fullscreenchange', function (e, data) {
25540        return _this4.handleTechFullscreenChange_(e, data);
25541      });
25542      this.on(this.tech_, 'fullscreenerror', function (e, err) {
25543        return _this4.handleTechFullscreenError_(e, err);
25544      });
25545      this.on(this.tech_, 'enterpictureinpicture', function (e) {
25546        return _this4.handleTechEnterPictureInPicture_(e);
25547      });
25548      this.on(this.tech_, 'leavepictureinpicture', function (e) {
25549        return _this4.handleTechLeavePictureInPicture_(e);
25550      });
25551      this.on(this.tech_, 'error', function (e) {
25552        return _this4.handleTechError_(e);
25553      });
25554      this.on(this.tech_, 'posterchange', function (e) {
25555        return _this4.handleTechPosterChange_(e);
25556      });
25557      this.on(this.tech_, 'textdata', function (e) {
25558        return _this4.handleTechTextData_(e);
25559      });
25560      this.on(this.tech_, 'ratechange', function (e) {
25561        return _this4.handleTechRateChange_(e);
25562      });
25563      this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
25564      this.usingNativeControls(this.techGet_('controls'));
25565
25566      if (this.controls() && !this.usingNativeControls()) {
vendor: 1,850 bytes, lines 25567-25624
25567        this.addTechControlsListeners_();
25568      } // Add the tech element in the DOM if it was not already there
25569      // Make sure to not insert the original video element if using Html5
25570
25571
25572      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
25573        prependTo(this.tech_.el(), this.el());
25574      } // Get rid of the original video tag reference after the first tech is loaded
25575
25576
25577      if (this.tag) {
25578        this.tag.player = null;
25579        this.tag = null;
25580      }
25581    }
25582    /**
25583     * Unload and dispose of the current playback {@link Tech}.
25584     *
25585     * @private
25586     */
25587    ;
25588
25589    _proto.unloadTech_ = function unloadTech_() {
25590      var _this5 = this;
25591
25592      // Save the current text tracks so that we can reuse the same text tracks with the next tech
25593      ALL.names.forEach(function (name) {
25594        var props = ALL[name];
25595        _this5[props.privateName] = _this5[props.getterName]();
25596      });
25597      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
25598      this.isReady_ = false;
25599      this.tech_.dispose();
25600      this.tech_ = false;
25601
25602      if (this.isPosterFromTech_) {
25603        this.poster_ = '';
25604        this.trigger('posterchange');
25605      }
25606
25607      this.isPosterFromTech_ = false;
25608    }
25609    /**
25610     * Return a reference to the current {@link Tech}.
25611     * It will print a warning by default about the danger of using the tech directly
25612     * but any argument that is passed in will silence the warning.
25613     *
25614     * @param {*} [safety]
25615     *        Anything passed in to silence the warning
25616     *
25617     * @return {Tech}
25618     *         The Tech
25619     */
25620    ;
25621
25622    _proto.tech = function tech(safety) {
25623      if (safety === undefined) {
25624        log$1.warn('Using the tech directly can be dangerous. I hope you know 
25624what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n');
25625      }
25626
25627      return this.tech_;
25628    }
25629    /**
25630     * Set up click and touch listeners for the playback element
25631     *
25632     * - On desktops: a click on the video itself will toggle playback
25633     * - On mobile devices: a click on the video toggles controls
25634     *   which is done by toggling the user state between active and
25635     *   inactive
25636     * - A tap can signal that a user has become active or has become inactive
25637     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
25638     *   quick tap should hide them again (signaling the user is in an inactive
25639     *   viewing state)
25640     * - In addition to this, we still want the user to be considered inactive after
25641     *   a few seconds of inactivity.
25642     *
25643     * > Note: the only part of iOS interaction we can't mimic with this setup
25644     * is a touch and hold on the video element counting as activity in order to
25645     * keep the controls showing, but that shouldn't be an issue. A touch and hold
25646     * on any controls will still keep the user active
25647     *
25648     * @private
25649     */
25650    ;
25651
25652    _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
25653      // Make sure to remove all the previous listeners in case we are called multiple times.
25654      this.removeTechControlsListeners_();
25655      this.on(this.tech_, 'click', this.boundHandleTechClick_);
25656      this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
25657      // so we'll check if the controls were already showing before reporting user
25658      // activity
25659
25660      this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
25661      this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
25662      this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
25663      // listener cancels out any reportedUserActivity when setting userActive(false)
25664
25665      this.on(this.tech_, 'tap', this.boundHandleTechTap_);
25666    }
25667    /**
25668     * Remove the listeners used for click and tap controls. This is needed for
25669     * toggling to controls disabled, where a tap/touch should do nothing.
25670     *
25671     * @private
25672     */
25673    ;
25674
25675    _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
25676      // We don't want to just use `this.off()` because there might be other needed
25677      // listeners added by techs that extend this.
25678      this.off(this.tech_, 'tap', this.boundHandleTechTap_);
25679      this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
25680      this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
25681      this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
25682      this.off(this.tech_, 'click', this.boundHandleTechClick_);
25683      this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
25684    }
25685    /**
25686     * Player waits for the tech to be ready
25687     *
25688     * @private
25689     */
25690    ;
25691
25692    _proto.handleTechReady_ = function handleTechReady_() {
25693      this.triggerReady(); // Keep the same volume as before
25694
25695      if (this.cache_.volume) {
25696        this.techCall_('setVolume', this.cache_.volume);
25697      } // Look if the tech found a higher resolution poster while loading
25698
25699
25700      this.handleTechPosterChange_(); // Update the duration if available
25701
25702      this.handleTechDurationChange_();
25703    }
25704    /**
25705     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
25706     * function will also trigger {@link Player#firstplay} if it is the first loadstart
25707     * for a video.
25708     *
25709     * @fires Player#loadstart
25710     * @fires Player#firstplay
25711     * @listens Tech#loadstart
25712     * @private
25713     */
25714    ;
25715
25716    _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
25717      // TODO: Update to use `emptied` event instead. See #1277.
25718      this.removeClass('vjs-ended');
25719      this.removeClass('vjs-seeking'); // reset the error state
25720
25721      this.error(null); // Update the duration
25722
25723      this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
25724      // The firstplay event relies on both the play and loadstart events
25725      // which can happen in any order for a new source
25726
25727      if (!this.paused()) {
25728        /**
25729         * Fired when the user agent begins looking for media data
25730         *
25731         * @event Player#loadstart
25732         * @type {EventTarget~Event}
25733         */
25734        this.trigger('loadstart');
25735        this.trigger('firstplay');
25736      } else {
25737        // reset the hasStarted state
25738        this.hasStarted(false);
25739        this.trigger('loadstart');
25740      } // autoplay happens after loadstart for the browser,
25741      // so we mimic that behavior
25742
25743
25744      this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
25745    }
25746    /**
25747     * Handle autoplay string values, rather than the typical boolean
25748     * values that should be handled by the tech. Note that this is not
25749     * part of any specification. Valid values and what they do can be
25750     * found on the autoplay getter at Player#autoplay()
25751     */
25752    ;
25753
25754    _proto.manualAutoplay_ = function manualAutoplay_(type) {
25755      var _this6 = this;
25756
25757      if (!this.tech_ || typeof type !== 'string') {
25758        return;
25759      } // Save original muted() value, set muted to true, and attempt to play().
25760      // On promise rejection, restore muted from saved value
25761
25762
25763      var resolveMuted = function resolveMuted() {
25764        var previouslyMuted = _this6.muted();
25765
25766        _this6.muted(true);
25767
25768        var restoreMuted = function restoreMuted() {
25769          _this6.muted(previouslyMuted);
25770        }; // restore muted on play terminatation
25771
25772
25773        _this6.playTerminatedQueue_.push(restoreMuted);
25774
25775        var mutedPromise = _this6.play();
25776
25777        if (!isPromise(mutedPromise)) {
25778          return;
25779        }
25780
25781        return mutedPromise["catch"](function (err) {
25782          restoreMuted();
25783          throw new Error("Rejection at manualAutoplay. Restoring muted value. " + (err ? err : ''));
25784        });
25785      };
25786
25787      var promise; // if muted defaults to true
25788      // the only thing we can do is call play
25789
25790      if (type === 'any' && !this.muted()) {
25791        promise = this.play();
25792
25793        if (isPromise(promise)) {
25794          promise = promise["catch"](resolveMuted);
25795        }
25796      } else if (type === 'muted' && !this.muted()) {
25797        promise = resolveMuted();
25798      } else {
25799        promise = this.play();
25800      }
25801
25802      if (!isPromise(promise)) {
25803        return;
25804      }
25805
25806      return promise.then(function () {
25807        _this6.trigger({
25808          type: 'autoplay-success',
25809          autoplay: type
25810        });
25811      })["catch"](function () {
25812        _this6.trigger({
25813          type: 'autoplay-failure',
25814          autoplay: type
25815        });
25816      });
25817    }
25818    /**
25819     * Update the internal source caches so that we return the correct source from
25820     * `src()`, `currentSource()`, and `currentSources()`.
25821     *
25822     * > Note: `currentSources` will not be updated if the source that is passed in exists
25823     *         in the current `currentSources` cache.
25824     *
25825     *
25826     * @param {Tech~SourceObject} srcObj
25827     *        A string or object source to update our caches to.
25828     */
25829    ;
25830
25831    _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
25832      if (srcObj === void 0) {
25833        srcObj = '';
25834      }
25835
25836      var src = srcObj;
25837      var type = '';
25838
25839      if (typeof src !== 'string') {
25840        src = srcObj.src;
25841        type = srcObj.type;
25842      } // make sure all the caches are set to default values
25843      // to prevent null checking
25844
25845
25846      this.cache_.source = this.cache_.source || {};
25847      this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
25848
25849      if (src && !type) {
25850        type = findMimetype(this, src);
25851      } // update `currentSource` cache always
25852
25853
25854      this.cache_.source = mergeOptions$3({}, srcObj, {
25855        src: src,
25856        type: type
25857      });
25858      var matchingSources = this.cache_.sources.filter(function (s) {
25859        return s.src && s.src === src;
25860      });
25861      var sourceElSources = [];
25862      var sourceEls = this.$$('source');
25863      var matchingSourceEls = [];
25864
25865      for (var i = 0; i < sourceEls.length; i++) {
25866        var sourceObj = getAttributes(sourceEls[i]);
25867        sourceElSources.push(sourceObj);
25868
25869        if (sourceObj.src && sourceObj.src === src) {
25870          matchingSourceEls.push(sourceObj.src);
25871        }
25872      } // if we have matching source els but not matching sources
25873      // the current source cache is not up to date
25874
25875
25876      if (matchingSourceEls.length && !matchingSources.length) {
25877        this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
25878        // sources cache to the `currentSource` cache
25879      } else if (!matchingSources.length) {
25880        this.cache_.sources = [this.cache_.source];
25881      } // update the tech `src` cache
25882
25883
25884      this.cache_.src = src;
25885    }
25886    /**
25887     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
25888     * causing the media element to reload.
25889     *
25890     * It will fire for the initial source and each subsequent source.
25891     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
25892     *
25893     * The event object for this event contains a `src` property that will contain the source
25894     * that was available when the event was triggered. This is generally only necessary if Video.js
25895     * is switching techs while the source was being changed.
25896     *
25897     * It is also fired when `load` is called on the player (or media element)
25898     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
25899     * says that the resource selection algorithm needs to be aborted and restarted.
25900     * In this case, it is very likely that the `src` property will be set to the
25901     * empty string `""` to indicate we do not know what the source will be but
25902     * that it is changing.
25903     *
25904     * *This event is currently still experimental and may change in minor releases.*
25905     * __To use this, pass `enableSourceset` option to the player.__
25906     *
25907     * @event Player#sourceset
25908     * @type {EventTarget~Event}
25909     * @prop {string} src
25910     *                The source url available when the `sourceset` was triggered.
25911     *                It will be an empty string if we cannot know what the source is
25912     *                but know that the source will change.
25913     */
25914
25915    /**
25916     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
25917     *
25918     * @fires Player#sourceset
25919     * @listens Tech#sourceset
25920     * @private
25921     */
25922    ;
25923
25924    _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
25925      var _this7 = this;
25926
25927      // only update the source cache when the source
25928      // was not updated using the player api
25929      if (!this.changingSrc_) {
25930        var updateSourceCaches = function updateSourceCaches(src) {
25931          return _this7.updateSourceCaches_(src);
25932        };
25933
25934        var playerSrc = this.currentSource().src;
25935        var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
25936
25937        if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
25938          // if both the tech source and the player source were updated we assume
25939          // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
25940          if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
25941            updateSourceCaches = function updateSourceCaches() {};
25942          }
25943        } // update the source to the initial source right away
25944        // in some cases this will be empty string
25945
25946
25947        updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
25948        // wait for a `loadstart` to update the cache to `currentSrc`.
25949        // If a sourceset happens before a `loadstart`, we reset the state
25950
25951        if (!event.src) {
25952          this.tech_.any(['sourceset', 'loadstart'], function (e) {
25953            // if a sourceset happens before a `loadstart` there
25954            // is nothing to do as this `handleTechSourceset_`
25955            // will be called again and this will be handled there.
25956            if (e.type === 'sourceset') {
25957              return;
25958            }
25959
25960            var techSrc = _this7.techGet('currentSrc');
25961
25962            _this7.lastSource_.tech = techSrc;
25963
25964            _this7.updateSourceCaches_(techSrc);
25965          });
25966        }
25967      }
25968
25969      this.lastSource_ = {
25970        player: this.currentSource().src,
25971        tech: event.src
25972      };
25973      this.trigger({
25974        src: event.src,
25975        type: 'sourceset'
25976      });
25977    }
25978    /**
25979     * Add/remove the vjs-has-started class
25980     *
25981     * @fires Player#firstplay
25982     *
25983     * @param {boolean} request
25984     *        - true: adds the class
25985     *        - false: remove the class
25986     *
25987     * @return {boolean}
25988     *         the boolean value of hasStarted_
25989     */
25990    ;
25991
25992    _proto.hasStarted = function hasStarted(request) {
25993      if (request === undefined) {
25994        // act as getter, if we have no request to change
25995        return this.hasStarted_;
25996      }
25997
25998      if (request === this.hasStarted_) {
25999        return;
26000      }
26001
26002      this.hasStarted_ = request;
26003
26004      if (this.hasStarted_) {
26005        this.addClass('vjs-has-started');
26006        this.trigger('firstplay');
26007      } else {
26008        this.removeClass('vjs-has-started');
26009      }
26010    }
26011    /**
26012     * Fired whenever the media begins or resumes playback
26013     *
26014     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
26015     * @fires Player#play
26016     * @listens Tech#play
26017     * @private
26018     */
26019    ;
26020
26021    _proto.handleTechPlay_ = function handleTechPlay_() {
26022      this.removeClass('vjs-ended');
26023      this.removeClass('vjs-paused');
26024      this.addClass('vjs-playing'); // hide the poster when the user hits play
26025
26026      this.hasStarted(true);
26027      /**
26028       * Triggered whenever an {@link Tech#play} event happens. Indicates that
26029       * playback has started or resumed.
26030       *
26031       * @event Player#play
26032       * @type {EventTarget~Event}
26033       */
26034
26035      this.trigger('play');
26036    }
26037    /**
26038     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
26039     *
26040     * If there were any events queued while the playback rate was zero, fire
26041     * those events now.
26042     *
26043     * @private
26044     * @method Player#handleTechRateChange_
26045     * @fires Player#ratechange
26046     * @listens Tech#ratechange
26047     */
26048    ;
26049
26050    _proto.handleTechRateChange_ = function handleTechRateChange_() {
26051      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
26052        this.queuedCallbacks_.forEach(function (queued) {
26053          return queued.callback(queued.event);
26054        });
26055        this.queuedCallbacks_ = [];
26056      }
26057
26058      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
26059      /**
26060       * Fires when the playing speed of the audio/video is changed
26061       *
26062       * @event Player#ratechange
26063       * @type {event}
26064       */
26065
26066      this.trigger('ratechange');
26067    }
26068    /**
26069     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
26070     *
26071     * @fires Player#waiting
26072     * @listens Tech#waiting
26073     * @private
26074     */
26075    ;
26076
26077    _proto.handleTechWaiting_ = function handleTechWaiting_() {
26078      var _this8 = this;
26079
26080      this.addClass('vjs-waiting');
26081      /**
26082       * A readyState change on the DOM element has caused playback to stop.
26083       *
26084       * @event Player#waiting
26085       * @type {EventTarget~Event}
26086       */
26087
26088      this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
26089      // premature removal of the waiting class, wait for the time to change.
26090
26091      var timeWhenWaiting = this.currentTime();
26092
26093      var timeUpdateListener = function timeUpdateListener() {
26094        if (timeWhenWaiting !== _this8.currentTime()) {
26095          _this8.removeClass('vjs-waiting');
26096
26097          _this8.off('timeupdate', timeUpdateListener);
26098        }
26099      };
26100
26101      this.on('timeupdate', timeUpdateListener);
26102    }
26103    /**
26104     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
26105     * > Note: This is not consistent between browsers. See #1351
26106     *
26107     * @fires Player#canplay
26108     * @listens Tech#canplay
26109     * @private
26110     */
26111    ;
26112
26113    _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
26114      this.removeClass('vjs-waiting');
26115      /**
26116       * The media has a readyState of HAVE_FUTURE_DATA or greater.
26117       *
26118       * @event Player#canplay
26119       * @type {EventTarget~Event}
26120       */
26121
26122      this.trigger('canplay');
26123    }
26124    /**
26125     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
26126     *
26127     * @fires Player#canplaythrough
26128     * @listens Tech#canplaythrough
26129     * @private
26130     */
26131    ;
26132
26133    _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
26134      this.removeClass('vjs-waiting');
26135      /**
26136       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
26137       * entire media file can be played without buffering.
26138       *
26139       * @event Player#canplaythrough
26140       * @type {EventTarget~Event}
26141       */
26142
26143      this.trigger('canplaythrough');
26144    }
26145    /**
26146     * Retrigger the `playing` event that was triggered by the {@link Tech}.
26147     *
26148     * @fires Player#playing
26149     * @listens Tech#playing
26150     * @private
26151     */
26152    ;
26153
26154    _proto.handleTechPlaying_ = function handleTechPlaying_() {
26155      this.removeClass('vjs-waiting');
26156      /**
26157       * The media is no longer blocked from playback, and has started playing.
26158       *
26159       * @event Player#playing
26160       * @type {EventTarget~Event}
26161       */
26162
26163      this.trigger('playing');
26164    }
26165    /**
26166     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
26167     *
26168     * @fires Player#seeking
26169     * @listens Tech#seeking
26170     * @private
26171     */
26172    ;
26173
26174    _proto.handleTechSeeking_ = function handleTechSeeking_() {
26175      this.addClass('vjs-seeking');
26176      /**
26177       * Fired whenever the player is jumping to a new time
26178       *
26179       * @event Player#seeking
26180       * @type {EventTarget~Event}
26181       */
26182
26183      this.trigger('seeking');
26184    }
26185    /**
26186     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
26187     *
26188     * @fires Player#seeked
26189     * @listens Tech#seeked
26190     * @private
26191     */
26192    ;
26193
26194    _proto.handleTechSeeked_ = function handleTechSeeked_() {
26195      this.removeClass('vjs-seeking');
26196      this.removeClass('vjs-ended');
26197      /**
26198       * Fired when the player has finished jumping to a new time
26199       *
26200       * @event Player#seeked
26201       * @type {EventTarget~Event}
26202       */
26203
26204      this.trigger('seeked');
26205    }
26206    /**
26207     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
26208     *
26209     * @fires Player#firstplay
26210     * @listens Tech#firstplay
26211     * @deprecated As of 6.0 firstplay event is deprecated.
26212     *             As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
26213     * @private
26214     */
26215    ;
26216
26217    _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
26218      // If the first starttime attribute is specified
26219      // then we will start at the given offset in seconds
26220      if (this.options_.starttime) {
26221        log$1.warn('Passing the `starttime` option to the player will be deprecated 
26221in 6.0');
26222        this.currentTime(this.options_.starttime);
26223      }
26224
26225      this.addClass('vjs-has-started');
26226      /**
26227       * Fired the first time a video is played. Not part of the HLS spec, and this is
26228       * probably not the best implementation yet, so use sparingly. If you don't have a
26229       * reason to prevent playback, use `myPlayer.one('play');` instead.
26230       *
26231       * @event Player#firstplay
26232       * @deprecated As of 6.0 firstplay event is deprecated.
26233       * @type {EventTarget~Event}
26234       */
26235
26236      this.trigger('firstplay');
26237    }
26238    /**
26239     * Retrigger the `pause` event that was triggered by the {@link Tech}.
26240     *
26241     * @fires Player#pause
26242     * @listens Tech#pause
26243     * @private
26244     */
26245    ;
26246
26247    _proto.handleTechPause_ = function handleTechPause_() {
26248      this.removeClass('vjs-playing');
26249      this.addClass('vjs-paused');
26250      /**
26251       * Fired whenever the media has been paused
26252       *
26253       * @event Player#pause
26254       * @type {EventTarget~Event}
26255       */
26256
26257      this.trigger('pause');
26258    }
26259    /**
26260     * Retrigger the `ended` event that was triggered by the {@link Tech}.
26261     *
26262     * @fires Player#ended
26263     * @listens Tech#ended
26264     * @private
26265     */
26266    ;
26267
26268    _proto.handleTechEnded_ = function handleTechEnded_() {
26269      this.addClass('vjs-ended');
26270      this.removeClass('vjs-waiting');
26271
26272      if (this.options_.loop) {
26273        this.currentTime(0);
26274        this.play();
26275      } else if (!this.paused()) {
26276        this.pause();
26277      }
26278      /**
26279       * Fired when the end of the media resource is reached (currentTime == duration)
26280       *
26281       * @event Player#ended
26282       * @type {EventTarget~Event}
26283       */
26284
26285
26286      this.trigger('ended');
26287    }
26288    /**
26289     * Fired when the duration of the media resource is first known or changed
26290     *
26291     * @listens Tech#durationchange
26292     * @private
26293     */
26294    ;
26295
26296    _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
26297      this.duration(this.techGet_('duration'));
26298    }
26299    /**
26300     * Handle a click on the media element to play/pause
26301     *
26302     * @param {EventTarget~Event} event
26303     *        the event that caused this function to trigger
26304     *
26305     * @listens Tech#click
26306     * @private
26307     */
26308    ;
26309
26310    _proto.handleTechClick_ = function handleTechClick_(event) {
26311      // When controls are disabled a click should not toggle playback because
26312      // the click is considered a control
26313      if (!this.controls_) {
26314        return;
26315      }
26316
26317      if (this.paused()) {
26318        silencePromise(this.play());
26319      } else {
26320        this.pause();
26321      }
26322    }
26323    /**
26324     * Handle a double-click on the media element to enter/exit fullscreen
26325     *
26326     * @param {EventTarget~Event} event
26327     *        the event that caused this function to trigger
26328     *
26329     * @listens Tech#dblclick
26330     * @private
26331     */
26332    ;
26333
26334    _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
26335      if (!this.controls_) {
26336        return;
26337      } // we do not want to toggle fullscreen state
26338      // when double-clicking inside a control bar or a modal
26339
26340
26341      var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
26342        return el.contains(event.target);
26343      });
26344
26345      if (!inAllowedEls) {
26346        /*
26347         * options.userActions.doubleClick
26348         *
26349         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
26350         * Set to `false` to disable double-click handling
26351         * Set to a function to substitute an external double-click handler
26352         */
26353        if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
26354          if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
26355            this.options_.userActions.doubleClick.call(this, event);
26356          } else if (this.isFullscreen()) {
26357            this.exitFullscreen();
26358          } else {
26359            this.requestFullscreen();
26360          }
26361        }
26362      }
26363    }
26364    /**
26365     * Handle a tap on the media element. It will toggle the user
26366     * activity state, which hides and shows the controls.
26367     *
26368     * @listens Tech#tap
26369     * @private
26370     */
26371    ;
26372
26373    _proto.handleTechTap_ = function handleTechTap_() {
26374      this.userActive(!this.userActive());
26375    }
26376    /**
26377     * Handle touch to start
26378     *
26379     * @listens Tech#touchstart
26380     * @private
26381     */
26382    ;
26383
26384    _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
26385      this.userWasActive = this.userActive();
26386    }
26387    /**
26388     * Handle touch to move
26389     *
26390     * @listens Tech#touchmove
26391     * @private
26392     */
26393    ;
26394
26395    _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
26396      if (this.userWasActive) {
26397        this.reportUserActivity();
26398      }
26399    }
26400    /**
26401     * Handle touch to end
26402     *
26403     * @param {EventTarget~Event} event
26404     *        the touchend event that triggered
26405     *        this function
26406     *
26407     * @listens Tech#touchend
26408     * @private
26409     */
26410    ;
26411
26412    _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
26413      // Stop the mouse events from also happening
26414      if (event.cancelable) {
26415        event.preventDefault();
26416      }
26417    }
26418    /**
26419     * native click events on the SWF aren't triggered on IE11, Win8.1RT
26420     * use stageclick events triggered from inside the SWF instead
26421     *
26422     * @private
26423     * @listens stageclick
26424     */
26425    ;
26426
26427    _proto.handleStageClick_ = function handleStageClick_() {
26428      this.reportUserActivity();
26429    }
26430    /**
26431     * @private
26432     */
26433    ;
26434
26435    _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
26436      if (this.isFullscreen()) {
26437        this.addClass('vjs-fullscreen');
26438      } else {
26439        this.removeClass('vjs-fullscreen');
26440      }
26441    }
26442    /**
26443     * when the document fschange event triggers it calls this
26444     */
26445    ;
26446
26447    _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
26448      var targetPlayer = e.target.player; // if another player was fullscreen
26449      // do a null check for targetPlayer because older firefox's would put document as e.target
26450
26451      if (targetPlayer && targetPlayer !== this) {
26452        return;
26453      }
26454
26455      var el = this.el();
26456      var isFs = document_1[this.fsApi_.fullscreenElement] === el;
26457
26458      if (!isFs && el.matches) {
26459        isFs = el.matches(':' + this.fsApi_.fullscreen);
26460      } else if (!isFs && el.msMatchesSelector) {
26461        isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
26462      }
26463
26464      this.isFullscreen(isFs);
26465    }
26466    /**
26467     * Handle Tech Fullscreen Change
26468     *
26469     * @param {EventTarget~Event} event
26470     *        the fullscreenchange event that triggered this function
26471     *
26472     * @param {Object} data
26473     *        the data that was sent with the event
26474     *
26475     * @private
26476     * @listens Tech#fullscreenchange
26477     * @fires Player#fullscreenchange
26478     */
26479    ;
26480
26481    _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
26482      if (data) {
26483        if (data.nativeIOSFullscreen) {
26484          this.toggleClass('vjs-ios-native-fs');
26485        }
26486
26487        this.isFullscreen(data.isFullscreen);
26488      }
26489    };
26490
26491    _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) {
26492      this.trigger('fullscreenerror', err);
26493    }
26494    /**
26495     * @private
26496     */
26497    ;
26498
26499    _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() {
26500      if (this.isInPictureInPicture()) {
26501        this.addClass('vjs-picture-in-picture');
26502      } else {
26503        this.removeClass('vjs-picture-in-picture');
26504      }
26505    }
26506    /**
26507     * Handle Tech Enter Picture-in-Picture.
26508     *
26509     * @param {EventTarget~Event} event
26510     *        the enterpictureinpicture event that triggered this function
26511     *
26512     * @private
26513     * @listens Tech#enterpictureinpicture
26514     */
26515    ;
26516
26517    _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) {
26518      this.isInPictureInPicture(true);
26519    }
26520    /**
26521     * Handle Tech Leave Picture-in-Picture.
26522     *
26523     * @param {EventTarget~Event} event
26524     *        the leavepictureinpicture event that triggered this function
26525     *
26526     * @private
26527     * @listens Tech#leavepictureinpicture
26528     */
26529    ;
26530
26531    _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) {
26532      this.isInPictureInPicture(false);
26533    }
26534    /**
26535     * Fires when an error occurred during the loading of an audio/video.
26536     *
26537     * @private
26538     * @listens Tech#error
26539     */
26540    ;
26541
26542    _proto.handleTechError_ = function handleTechError_() {
26543      var error = this.tech_.error();
26544      this.error(error);
26545    }
26546    /**
26547     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
26548     *
26549     * @fires Player#textdata
26550     * @listens Tech#textdata
26551     * @private
26552     */
26553    ;
26554
26555    _proto.handleTechTextData_ = function handleTechTextData_() {
26556      var data = null;
26557
26558      if (arguments.length > 1) {
26559        data = arguments[1];
26560      }
26561      /**
26562       * Fires when we get a textdata event from tech
26563       *
26564       * @event Player#textdata
26565       * @type {EventTarget~Event}
26566       */
26567
26568
26569      this.trigger('textdata', data);
26570    }
26571    /**
26572     * Get object for cached values.
26573     *
26574     * @return {Object}
26575     *         get the current object cache
26576     */
26577    ;
26578
26579    _proto.getCache = function getCache() {
26580      return this.cache_;
26581    }
26582    /**
26583     * Resets the internal cache object.
26584     *
26585     * Using this function outside the player constructor or reset method may
26586     * have unintended side-effects.
26587     *
26588     * @private
26589     */
26590    ;
26591
26592    _proto.resetCache_ = function resetCache_() {
26593      this.cache_ = {
26594        // Right now, the currentTime is not _really_ cached because it is always
26595        // retrieved from the tech (see: currentTime). However, for completeness,
26596        // we set it to zero here to ensure that if we do start actually caching
26597        // it, we reset it along with everything else.
26598        currentTime: 0,
26599        initTime: 0,
26600        inactivityTimeout: this.options_.inactivityTimeout,
26601        duration: NaN,
26602        lastVolume: 1,
26603        lastPlaybackRate: this.defaultPlaybackRate(),
26604        media: null,
26605        src: '',
26606        source: {},
26607        sources: [],
26608        playbackRates: [],
26609        volume: 1
26610      };
26611    }
26612    /**
26613     * Pass values to the playback tech
26614     *
26615     * @param {string} [method]
26616     *        the method to call
26617     *
26618     * @param {Object} arg
26619     *        the argument to pass
26620     *
26621     * @private
26622     */
26623    ;
26624
26625    _proto.techCall_ = function techCall_(method, arg) {
26626      // If it's not ready yet, call method when it is
26627      this.ready(function () {
26628        if (method in allowedSetters) {
26629          return set(this.middleware_, this.tech_, method, arg);
26630        } else if (method in allowedMediators) {
26631          return mediate(this.middleware_, this.tech_, method, arg);
26632        }
26633
26634        try {
26635          if (this.tech_) {
26636            this.tech_[method](arg);
26637          }
26638        } catch (e) {
26639          log$1(e);
26640          throw e;
26641        }
26642      }, true);
26643    }
26644    /**
26645     * Get calls can't wait for the tech, and sometimes don't need to.
26646     *
26647     * @param {string} method
26648     *        Tech method
26649     *
26650     * @return {Function|undefined}
26651     *         the method or undefined
26652     *
26653     * @private
26654     */
26655    ;
26656
26657    _proto.techGet_ = function techGet_(method) {
26658      if (!this.tech_ || !this.tech_.isReady_) {
26659        return;
26660      }
26661
26662      if (method in allowedGetters) {
26663        return get(this.middleware_, this.tech_, method);
26664      } else if (method in allowedMediators) {
26665        return mediate(this.middleware_, this.tech_, method);
26666      } // Flash likes to die and reload when you hide or reposition it.
26667      // In these cases the object methods go away and we get errors.
26668      // TODO: Is this needed for techs other than Flash?
26669      // When that happens we'll catch the errors and inform tech that it's not ready any more.
26670
26671
26672      try {
26673        return this.tech_[method]();
26674      } catch (e) {
26675        // When building additional tech libs, an expected method may not be defined yet
26676        if (this.tech_[method] === undefined) {
26677          log$1("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
26678          throw e;
26679        } // When a method isn't available on the object it throws a TypeError
26680
26681
26682        if (e.name === 'TypeError') {
26683          log$1("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
26684          this.tech_.isReady_ = false;
26685          throw e;
26686        } // If error unknown, just log and throw
26687
26688
26689        log$1(e);
26690        throw e;
26691      }
26692    }
26693    /**
26694     * Attempt to begin playback at the first opportunity.
26695     *
26696     * @return {Promise|undefined}
26697     *         Returns a promise if the browser supports Promises (or one
26698     *         was passed in as an option). This promise will be resolved on
26699     *         the return value of play. If this is undefined it will fulfill the
26700     *         promise chain otherwise the promise chain will be fulfilled when
26701     *         the promise from play is fulfilled.
26702     */
26703    ;
26704
26705    _proto.play = function play() {
26706      var _this9 = this;
26707
26708      var PromiseClass = this.options_.Promise || window_1.Promise;
26709
26710      if (PromiseClass) {
26711        return new PromiseClass(function (resolve) {
26712          _this9.play_(resolve);
26713        });
26714      }
26715
26716      return this.play_();
26717    }
26718    /**
26719     * The actual logic for play, takes a callback that will be resolved on the
26720     * return value of play. This allows us to resolve to the play promise if there
26721     * is one on modern browsers.
26722     *
26723     * @private
26724     * @param {Function} [callback]
26725     *        The callback that should be called when the techs play is actually called
26726     */
26727    ;
26728
26729    _proto.play_ = function play_(callback) {
26730      var _this10 = this;
26731
26732      if (callback === void 0) {
26733        callback = silencePromise;
26734      }
26735
26736      this.playCallbacks_.push(callback);
26737      var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
26738
26739      if (this.waitToPlay_) {
26740        this.off(['ready', 'loadstart'], this.waitToPlay_);
26741        this.waitToPlay_ = null;
26742      } // if the player/tech is not ready or the src itself is not ready
26743      // queue up a call to play on `ready` or `loadstart`
26744
26745
26746      if (!this.isReady_ || !isSrcReady) {
26747        this.waitToPlay_ = function (e) {
26748          _this10.play_();
26749        };
26750
26751        this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
26752        // in that case, we need to prime the video element by calling load so it'll be ready in time
26753
26754        if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
26755          this.load();
26756        }
26757
26758        return;
26759      } // If the player/tech is ready and we have a source, we can attempt playback.
26760
26761
26762      var val = this.techGet_('play'); // play was terminated if the returned value is null
26763
26764      if (val === null) {
26765        this.runPlayTerminatedQueue_();
26766      } else {
26767        this.runPlayCallbacks_(val);
26768      }
26769    }
26770    /**
26771     * These functions will be run when if play is terminated. If play
26772     * runPlayCallbacks_ is run these function will not be run. This allows us
26773     * to differenciate between a terminated play and an actual call to play.
26774     */
26775    ;
26776
26777    _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
26778      var queue = this.playTerminatedQueue_.slice(0);
26779      this.playTerminatedQueue_ = [];
26780      queue.forEach(function (q) {
26781        q();
26782      });
26783    }
26784    /**
26785     * When a callback to play is delayed we have to run these
26786     * callbacks when play is actually called on the tech. This function
26787     * runs the callbacks that were delayed and accepts the return value
26788     * from the tech.
26789     *
26790     * @param {undefined|Promise} val
26791     *        The return value from the tech.
26792     */
26793    ;
26794
26795    _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
26796      var callbacks = this.playCallbacks_.slice(0);
26797      this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
26798
26799      this.playTerminatedQueue_ = [];
26800      callbacks.forEach(function (cb) {
26801        cb(val);
26802      });
26803    }
26804    /**
26805     * Pause the video playback
26806     *
26807     * @return {Player}
26808     *         A reference to the player object this function was called on
26809     */
26810    ;
26811
26812    _proto.pause = function pause() {
26813      this.techCall_('pause');
26814    }
26815    /**
26816     * Check if the player is paused or has yet to play
26817     *
26818     * @return {boolean}
26819     *         - false: if the media is currently playing
26820     *         - true: if media is not currently playing
26821     */
26822    ;
26823
26824    _proto.paused = function paused() {
26825      // The initial state of paused should be true (in Safari it's actually false)
26826      return this.techGet_('paused') === false ? false : true;
26827    }
26828    /**
26829     * Get a TimeRange object representing the current ranges of time that the user
26830     * has played.
26831     *
26832     * @return {TimeRange}
26833     *         A time range object that represents all the increments of time that have
26834     *         been played.
26835     */
26836    ;
26837
26838    _proto.played = function played() {
26839      return this.techGet_('played') || createTimeRanges(0, 0);
26840    }
26841    /**
26842     * Returns whether or not the user is "scrubbing". Scrubbing is
26843     * when the user has clicked the progress bar handle and is
26844     * dragging it along the progress bar.
26845     *
26846     * @param {boolean} [isScrubbing]
26847     *        whether the user is or is not scrubbing
26848     *
26849     * @return {boolean}
26850     *         The value of scrubbing when getting
26851     */
26852    ;
26853
26854    _proto.scrubbing = function scrubbing(isScrubbing) {
26855      if (typeof isScrubbing === 'undefined') {
26856        return this.scrubbing_;
26857      }
26858
26859      this.scrubbing_ = !!isScrubbing;
26860      this.techCall_('setScrubbing', this.scrubbing_);
26861
26862      if (isScrubbing) {
26863        this.addClass('vjs-scrubbing');
26864      } else {
26865        this.removeClass('vjs-scrubbing');
26866      }
26867    }
26868    /**
26869     * Get or set the current time (in seconds)
26870     *
26871     * @param {number|string} [seconds]
26872     *        The time to seek to in seconds
26873     *
26874     * @return {number}
26875     *         - the current time in seconds when getting
26876     */
26877    ;
26878
26879    _proto.currentTime = function currentTime(seconds) {
26880      if (typeof seconds !== 'undefined') {
26881        if (seconds < 0) {
26882          seconds = 0;
26883        }
26884
26885        if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
26886          this.cache_.initTime = seconds;
26887          this.off('canplay', this.boundApplyInitTime_);
26888          this.one('canplay', this.boundApplyInitTime_);
26889          return;
26890        }
26891
26892        this.techCall_('setCurrentTime', seconds);
26893        this.cache_.initTime = 0;
26894        return;
26895      } // cache last currentTime and return. default to 0 seconds
26896      //
26897      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
26898      // currentTime when scrubbing, but may not provide much performance benefit afterall.
26899      // Should be tested. Also something has to read the actual current time or the cache will
26900      // never get updated.
26901
26902
26903      this.cache_.currentTime = this.techGet_('currentTime') || 0;
26904      return this.cache_.currentTime;
26905    }
26906    /**
26907     * Apply the value of initTime stored in cache as currentTime.
26908     *
26909     * @private
26910     */
26911    ;
26912
26913    _proto.applyInitTime_ = function applyInitTime_() {
26914      this.currentTime(this.cache_.initTime);
26915    }
26916    /**
26917     * Normally gets the length in time of the video in seconds;
26918     * in all but the rarest use cases an argument will NOT be passed to the method
26919     *
26920     * > **NOTE**: The video must have started loading before the duration can be
26921     * known, and depending on preload behaviour may not be known until the video starts
26922     * playing.
26923     *
26924     * @fires Player#durationchange
26925     *
26926     * @param {number} [seconds]
26927     *        The duration of the video to set in seconds
26928     *
26929     * @return {number}
26930     *         - The duration of the video in seconds when getting
26931     */
26932    ;
26933
26934    _proto.duration = function duration(seconds) {
26935      if (seconds === undefined) {
26936        // return NaN if the duration is not known
26937        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
26938      }
26939
26940      seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
26941
26942      if (seconds < 0) {
26943        seconds = Infinity;
26944      }
26945
26946      if (seconds !== this.cache_.duration) {
26947        // Cache the last set value for optimized scrubbing (esp. Flash)
26948        // TODO: Required for techs other than Flash?
26949        this.cache_.duration = seconds;
26950
26951        if (seconds === Infinity) {
26952          this.addClass('vjs-live');
26953        } else {
26954          this.removeClass('vjs-live');
26955        }
26956
26957        if (!isNaN(seconds)) {
26958          // Do not fire durationchange unless the duration value is known.
26959          // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
26960
26961          /**
26962           * @event Player#durationchange
26963           * @type {EventTarget~Event}
26964           */
26965          this.trigger('durationchange');
26966        }
26967      }
26968    }
26969    /**
26970     * Calculates how much time is left in the video. Not part
26971     * of the native video API.
26972     *
26973     * @return {number}
26974     *         The time remaining in seconds
26975     */
26976    ;
26977
26978    _proto.remainingTime = function remainingTime() {
26979      return this.duration() - this.currentTime();
26980    }
26981    /**
26982     * A remaining time function that is intented to be used when
26983     * the time is to be displayed directly to the user.
26984     *
26985     * @return {number}
26986     *         The rounded time remaining in seconds
26987     */
26988    ;
26989
26990    _proto.remainingTimeDisplay = function remainingTimeDisplay() {
26991      return Math.floor(this.duration()) - Math.floor(this.currentTime());
26992    } //
26993    // Kind of like an array of portions of the video that have been downloaded.
26994
26995    /**
26996     * Get a TimeRange object with an array of the times of the video
26997     * that have been downloaded. If you just want the percent of the
26998     * video that's been downloaded, use bufferedPercent.
26999     *
27000     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
27001     *
27002     * @return {TimeRange}
27003     *         A mock TimeRange object (following HTML spec)
27004     */
27005    ;
27006
27007    _proto.buffered = function buffered() {
27008      var buffered = this.techGet_('buffered');
27009
27010      if (!buffered || !buffered.length) {
27011        buffered = createTimeRanges(0, 0);
27012      }
27013
27014      return buffered;
27015    }
27016    /**
27017     * Get the percent (as a decimal) of the video that's been downloaded.
27018     * This method is not a part of the native HTML video API.
27019     *
27020     * @return {number}
27021     *         A decimal between 0 and 1 representing the percent
27022     *         that is buffered 0 being 0% and 1 being 100%
27023     */
27024    ;
27025
27026    _proto.bufferedPercent = function bufferedPercent$1() {
27027      return bufferedPercent(this.buffered(), this.duration());
27028    }
27029    /**
27030     * Get the ending time of the last buffered time range
27031     * This is used in the progress bar to encapsulate all time ranges.
27032     *
27033     * @return {number}
27034     *         The end of the last buffered time range
27035     */
27036    ;
27037
27038    _proto.bufferedEnd = function bufferedEnd() {
27039      var buffered = this.buffered();
27040      var duration = this.duration();
27041      var end = buffered.end(buffered.length - 1);
27042
27043      if (end > duration) {
27044        end = duration;
27045      }
27046
27047      return end;
27048    }
27049    /**
27050     * Get or set the current volume of the media
27051     *
27052     * @param  {number} [percentAsDecimal]
27053     *         The new volume as a decimal percent:
27054     *         - 0 is muted/0%/off
27055     *         - 1.0 is 100%/full
27056     *         - 0.5 is half volume or 50%
27057     *
27058     * @return {number}
27059     *         The current volume as a percent when getting
27060     */
27061    ;
27062
27063    _proto.volume = function volume(percentAsDecimal) {
27064      var vol;
27065
27066      if (percentAsDecimal !== undefined) {
27067        // Force value to between 0 and 1
27068        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
27069        this.cache_.volume = vol;
27070        this.techCall_('setVolume', vol);
27071
27072        if (vol > 0) {
27073          this.lastVolume_(vol);
27074        }
27075
27076        return;
27077      } // Default to 1 when returning current volume.
27078
27079
27080      vol = parseFloat(this.techGet_('volume'));
27081      return isNaN(vol) ? 1 : vol;
27082    }
27083    /**
27084     * Get the current muted state, or turn mute on or off
27085     *
27086     * @param {boolean} [muted]
27087     *        - true to mute
27088     *        - false to unmute
27089     *
27090     * @return {boolean}
27091     *         - true if mute is on and getting
27092     *         - false if mute is off and getting
27093     */
27094    ;
27095
27096    _proto.muted = function muted(_muted) {
27097      if (_muted !== undefined) {
27098        this.techCall_('setMuted', _muted);
27099        return;
27100      }
27101
27102      return this.techGet_('muted') || false;
27103    }
27104    /**
27105     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
27106     * indicates the state of muted on initial playback.
27107     *
27108     * ```js
27109     *   var myPlayer = videojs('some-player-id');
27110     *
27111     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
27112     *
27113     *   // get, should be false
27114     *   console.log(myPlayer.defaultMuted());
27115     *   // set to true
27116     *   myPlayer.defaultMuted(true);
27117     *   // get should be true
27118     *   console.log(myPlayer.defaultMuted());
27119     * ```
27120     *
27121     * @param {boolean} [defaultMuted]
27122     *        - true to mute
27123     *        - false to unmute
27124     *
27125     * @return {boolean|Player}
27126     *         - true if defaultMuted is on and getting
27127     *         - false if defaultMuted is off and getting
27128     *         - A reference to the current player when setting
27129     */
27130    ;
27131
27132    _proto.defaultMuted = function defaultMuted(_defaultMuted) {
27133      if (_defaultMuted !== undefined) {
27134        return this.techCall_('setDefaultMuted', _defaultMuted);
27135      }
27136
27137      return this.techGet_('defaultMuted') || false;
27138    }
27139    /**
27140     * Get the last volume, or set it
27141     *
27142     * @param  {number} [percentAsDecimal]
27143     *         The new last volume as a decimal percent:
27144     *         - 0 is muted/0%/off
27145     *         - 1.0 is 100%/full
27146     *         - 0.5 is half volume or 50%
27147     *
27148     * @return {number}
27149     *         the current value of lastVolume as a percent when getting
27150     *
27151     * @private
27152     */
27153    ;
27154
27155    _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
27156      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
27157        this.cache_.lastVolume = percentAsDecimal;
27158        return;
27159      }
27160
27161      return this.cache_.lastVolume;
27162    }
27163    /**
27164     * Check if current tech can support native fullscreen
27165     * (e.g. with built in controls like iOS)
27166     *
27167     * @return {boolean}
27168     *         if native fullscreen is supported
27169     */
27170    ;
27171
27172    _proto.supportsFullScreen = function supportsFullScreen() {
27173      return this.techGet_('supportsFullScreen') || false;
27174    }
27175    /**
27176     * Check if the player is in fullscreen mode or tell the player that it
27177     * is or is not in fullscreen mode.
27178     *
27179     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
27180     * property and instead document.fullscreenElement is used. But isFullscreen is
27181     * still a valuable property for internal player workings.
27182     *
27183     * @param  {boolean} [isFS]
27184     *         Set the players current fullscreen state
27185     *
27186     * @return {boolean}
27187     *         - true if fullscreen is on and getting
27188     *         - false if fullscreen is off and getting
27189     */
27190    ;
27191
27192    _proto.isFullscreen = function isFullscreen(isFS) {
27193      if (isFS !== undefined) {
27194        var oldValue = this.isFullscreen_;
27195        this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
27196        // this is the only place where we trigger fullscreenchange events for older browsers
27197        // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
27198
27199        if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
27200          /**
27201             * @event Player#fullscreenchange
27202             * @type {EventTarget~Event}
27203             */
27204          this.trigger('fullscreenchange');
27205        }
27206
27207        this.toggleFullscreenClass_();
27208        return;
27209      }
27210
27211      return this.isFullscreen_;
27212    }
27213    /**
27214     * Increase the size of the video to full screen
27215     * In some browsers, full screen is not supported natively, so it enters
27216     * "full window mode", where the video fills the browser window.
27217     * In browsers and devices that support native full screen, sometimes the
27218     * browser's default controls will be shown, and not the Video.js custom skin.
27219     * This includes most mobile devices (iOS, Android) and older versions of
27220     * Safari.
27221     *
27222     * @param  {Object} [fullscreenOptions]
27223     *         Override the player fullscreen options
27224     *
27225     * @fires Player#fullscreenchange
27226     */
27227    ;
27228
27229    _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) {
27230      var PromiseClass = this.options_.Promise || window_1.Promise;
27231
27232      if (PromiseClass) {
27233        var self = this;
27234        return new PromiseClass(function (resolve, reject) {
27235          function offHandler() {
27236            self.off('fullscreenerror', errorHandler);
27237            self.off('fullscreenchange', changeHandler);
27238          }
27239
27240          function changeHandler() {
27241            offHandler();
27242            resolve();
27243          }
27244
27245          function errorHandler(e, err) {
27246            offHandler();
27247            reject(err);
27248          }
27249
27250          self.one('fullscreenchange', changeHandler);
27251          self.one('fullscreenerror', errorHandler);
27252          var promise = self.requestFullscreenHelper_(fullscreenOptions);
27253
27254          if (promise) {
27255            promise.then(offHandler, offHandler);
27256            promise.then(resolve, reject);
27257          }
27258        });
27259      }
27260
27261      return this.requestFullscreenHelper_();
27262    };
27263
27264    _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) {
27265      var _this11 = this;
27266
27267      var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
27268      // Use defaults or player configured option unless passed directly to this method.
27269
27270      if (!this.fsApi_.prefixed) {
27271        fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
27272
27273        if (fullscreenOptions !== undefined) {
27274          fsOptions = fullscreenOptions;
27275        }
27276      } // This method works as follows:
27277      // 1. if a fullscreen api is available, use it
27278      //   1. call requestFullscreen with potential options
27279      //   2. if we got a promise from above, use it to update isFullscreen()
27280      // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
27281      //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
27282      // 3. otherwise, use "fullWindow" mode
27283
27284
27285      if (this.fsApi_.requestFullscreen) {
27286        var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
27287
27288        if (promise) {
27289          promise.then(function () {
27290            return _this11.isFullscreen(true);
27291          }, function () {
27292            return _this11.isFullscreen(false);
27293          });
27294        }
27295
27296        return promise;
27297      } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
27298        // we can't take the video.js controls fullscreen but we can go fullscreen
27299        // with native controls
27300        this.techCall_('enterFullScreen');
27301      } else {
27302        // fullscreen isn't supported so we'll just stretch the video element to
27303        // fill the viewport
27304        this.enterFullWindow();
27305      }
27306    }
27307    /**
27308     * Return the video to its normal size after having been in full screen mode
27309     *
27310     * @fires Player#fullscreenchange
27311     */
27312    ;
27313
27314    _proto.exitFullscreen = function exitFullscreen() {
27315      var PromiseClass = this.options_.Promise || window_1.Promise;
27316
27317      if (PromiseClass) {
27318        var self = this;
27319        return new PromiseClass(function (resolve, reject) {
27320          function offHandler() {
27321            self.off('fullscreenerror', errorHandler);
27322            self.off('fullscreenchange', changeHandler);
27323          }
27324
27325          function changeHandler() {
27326            offHandler();
27327            resolve();
27328          }
27329
27330          function errorHandler(e, err) {
27331            offHandler();
27332            reject(err);
27333          }
27334
27335          self.one('fullscreenchange', changeHandler);
27336          self.one('fullscreenerror', errorHandler);
27337          var promise = self.exitFullscreenHelper_();
27338
27339          if (promise) {
27340            promise.then(offHandler, offHandler); // map the promise to our resolve/reject methods
27341
27342            promise.then(resolve, reject);
27343          }
27344        });
27345      }
27346
27347      return this.exitFullscreenHelper_();
27348    };
27349
27350    _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() {
27351      var _this12 = this;
27352
27353      if (this.fsApi_.requestFullscreen) {
27354        var promise = document_1[this.fsApi_.exitFullscreen]();
27355
27356        if (promise) {
27357          // we're splitting the promise here, so, we want to catch the
27358          // potential error so that this chain doesn't have unhandled errors
27359          silencePromise(promise.then(function () {
27360            return _this12.isFullscreen(false);
27361          }));
27362        }
27363
27364        return promise;
27365      } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
27366        this.techCall_('exitFullScreen');
27367      } else {
27368        this.exitFullWindow();
27369      }
27370    }
27371    /**
27372     * When fullscreen isn't supported we can stretch the
27373     * video container to as wide as the browser will let us.
27374     *
27375     * @fires Player#enterFullWindow
27376     */
27377    ;
27378
27379    _proto.enterFullWindow = function enterFullWindow() {
27380      this.isFullscreen(true);
27381      this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
27382
27383      this.docOrigOverflow = document_1.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
27384
27385      on(document_1, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
27386
27387      document_1.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
27388
27389      addClass(document_1.body, 'vjs-full-window');
27390      /**
27391       * @event Player#enterFullWindow
27392       * @type {EventTarget~Event}
27393       */
27394
27395      this.trigger('enterFullWindow');
27396    }
27397    /**
27398     * Check for call to either exit full window or
27399     * full screen on ESC key
27400     *
27401     * @param {string} event
27402     *        Event to check for key press
27403     */
27404    ;
27405
27406    _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
27407      if (keycode.isEventKey(event, 'Esc')) {
27408        if (this.isFullscreen() === true) {
27409          if (!this.isFullWindow) {
27410            this.exitFullscreen();
27411          } else {
27412            this.exitFullWindow();
27413          }
27414        }
27415      }
27416    }
27417    /**
27418     * Exit full window
27419     *
27420     * @fires Player#exitFullWindow
27421     */
27422    ;
27423
27424    _proto.exitFullWindow = function exitFullWindow() {
27425      this.isFullscreen(false);
27426      this.isFullWindow = false;
27427      off(document_1, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
27428
27429      document_1.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
27430
27431      removeClass(document_1.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
27432      // this.positionAll();
27433
27434      /**
27435       * @event Player#exitFullWindow
27436       * @type {EventTarget~Event}
27437       */
27438
27439      this.trigger('exitFullWindow');
27440    }
27441    /**
27442     * Disable Picture-in-Picture mode.
27443     *
27444     * @param {boolean} value
27445     *                  - true will disable Picture-in-Picture mode
27446     *                  - false will enable Picture-in-Picture mode
27447     */
27448    ;
27449
27450    _proto.disablePictureInPicture = function disablePictureInPicture(value) {
27451      if (value === undefined) {
27452        return this.techGet_('disablePictureInPicture');
27453      }
27454
27455      this.techCall_('setDisablePictureInPicture', value);
27456      this.options_.disablePictureInPicture = value;
27457      this.trigger('disablepictureinpicturechanged');
27458    }
27459    /**
27460     * Check if the player is in Picture-in-Picture mode or tell the player that it
27461     * is or is not in Picture-in-Picture mode.
27462     *
27463     * @param  {boolean} [isPiP]
27464     *         Set the players current Picture-in-Picture state
27465     *
27466     * @return {boolean}
27467     *         - true if Picture-in-Picture is on and getting
27468     *         - false if Picture-in-Picture is off and getting
27469     */
27470    ;
27471
27472    _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) {
27473      if (isPiP !== undefined) {
27474        this.isInPictureInPicture_ = !!isPiP;
27475        this.togglePictureInPictureClass_();
27476        return;
27477      }
27478
27479      return !!this.isInPictureInPicture_;
27480    }
27481    /**
27482     * Create a floating video window always on top of other windows so that users may
27483     * continue consuming media while they interact with other content sites, or
27484     * applications on their device.
27485     *
27486     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
27487     *
27488     * @fires Player#enterpictureinpicture
27489     *
27490     * @return {Promise}
27491     *         A promise with a Picture-in-Picture window.
27492     */
27493    ;
27494
27495    _proto.requestPictureInPicture = function requestPictureInPicture() {
27496      if ('pictureInPictureEnabled' in document_1 && this.disablePictureInPicture() === false) {
27497        /**
27498         * This event fires when the player enters picture in picture mode
27499         *
27500         * @event Player#enterpictureinpicture
27501         * @type {EventTarget~Event}
27502         */
27503        return this.techGet_('requestPictureInPicture');
27504      }
27505    }
27506    /**
27507     * Exit Picture-in-Picture mode.
27508     *
27509     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
27510     *
27511     * @fires Player#leavepictureinpicture
27512     *
27513     * @return {Promise}
27514     *         A promise.
27515     */
27516    ;
27517
27518    _proto.exitPictureInPicture = function exitPictureInPicture() {
27519      if ('pictureInPictureEnabled' in document_1) {
27520        /**
27521         * This event fires when the player leaves picture in picture mode
27522         *
27523         * @event Player#leavepictureinpicture
27524         * @type {EventTarget~Event}
27525         */
27526        return document_1.exitPictureInPicture();
27527      }
27528    }
27529    /**
27530     * Called when this Player has focus and a key gets pressed down, or when
27531     * any Component of this player receives a key press that it doesn't handle.
27532     * This allows player-wide hotkeys (either as defined below, or optionally
27533     * by an external function).
27534     *
27535     * @param {EventTarget~Event} event
27536     *        The `keydown` event that caused this function to be called.
27537     *
27538     * @listens keydown
27539     */
27540    ;
27541
27542    _proto.handleKeyDown = function handleKeyDown(event) {
27543      var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
27544
27545      if (!userActions || !userActions.hotkeys) {
27546        return;
27547      } // Function that determines whether or not to exclude an element from
27548      // hotkeys handling.
27549
27550
27551      var excludeElement = function excludeElement(el) {
27552        var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements.
27553
27554        if (el.isContentEditable) {
27555          return true;
27556        } // Inputs matching these types will still trigger hotkey handling as
27557        // they are not text inputs.
27558
27559
27560        var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
27561
27562        if (tagName === 'input') {
27563          return allowedInputTypes.indexOf(el.type) === -1;
27564        } // The final test is by tag name. These tags will be excluded entirely.
27565
27566
27567        var excludedTags = ['textarea'];
27568        return excludedTags.indexOf(tagName) !== -1;
27569      }; // Bail out if the user is focused on an interactive form element.
27570
27571
27572      if (excludeElement(this.el_.ownerDocument.activeElement)) {
27573        return;
27574      }
27575
27576      if (typeof userActions.hotkeys === 'function') {
27577        userActions.hotkeys.call(this, event);
27578      } else {
27579        this.handleHotkeys(event);
27580      }
27581    }
27582    /**
27583     * Called when this Player receives a hotkey keydown event.
27584     * Supported player-wide hotkeys are:
27585     *
27586     *   f          - toggle fullscreen
27587     *   m          - toggle mute
27588     *   k or Space - toggle play/pause
27589     *
27590     * @param {EventTarget~Event} event
27591     *        The `keydown` event that caused this function to be called.
27592     */
27593    ;
27594
27595    _proto.handleHotkeys = function handleHotkeys(event) {
27596      var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
27597
27598      var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
27599          fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
27600        return keycode.isEventKey(keydownEvent, 'f');
27601      } : _hotkeys$fullscreenKe,
27602          _hotkeys$muteKey = hotkeys.muteKey,
27603          muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
27604        return keycode.isEventKey(keydownEvent, 'm');
27605      } : _hotkeys$muteKey,
27606          _hotkeys$playPauseKey = hotkeys.playPauseKey,
27607          playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
27608        return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
27609      } : _hotkeys$playPauseKey;
27610
27611      if (fullscreenKey.call(this, event)) {
27612        event.preventDefault();
27613        event.stopPropagation();
27614        var FSToggle = Component$1.getComponent('FullscreenToggle');
27615
27616        if (document_1[this.fsApi_.fullscreenEnabled] !== false) {
27617          FSToggle.prototype.handleClick.call(this, event);
27618        }
27619      } else if (muteKey.call(this, event)) {
27620        event.preventDefault();
27621        event.stopPropagation();
27622        var MuteToggle = Component$1.getComponent('MuteToggle');
27623        MuteToggle.prototype.handleClick.call(this, event);
27624      } else if (playPauseKey.call(this, event)) {
27625        event.preventDefault();
27626        event.stopPropagation();
27627        var PlayToggle = Component$1.getComponent('PlayToggle');
27628        PlayToggle.prototype.handleClick.call(this, event);
27629      }
27630    }
27631    /**
27632     * Check whether the player can play a given mimetype
27633     *
27634     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
27635     *
27636     * @param {string} type
27637     *        The mimetype to check
27638     *
27639     * @return {string}
27640     *         'probably', 'maybe', or '' (empty string)
27641     */
27642    ;
27643
27644    _proto.canPlayType = function canPlayType(type) {
27645      var can; // Loop through each playback technology in the options order
27646
27647      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
27648        var techName = j[i];
27649        var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
27650        // Remove once that deprecated behavior is removed.
27651
27652        if (!tech) {
27653          tech = Component$1.getComponent(techName);
27654        } // Check if the current tech is defined before continuing
27655
27656
27657        if (!tech) {
27658          log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
27659          continue;
27660        } // Check if the browser supports this technology
27661
27662
27663        if (tech.isSupported()) {
27664          can = tech.canPlayType(type);
27665
27666          if (can) {
27667            return can;
27668          }
27669        }
27670      }
27671
27672      return '';
27673    }
27674    /**
27675     * Select source based on tech-order or source-order
27676     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
27677     * defaults to tech-order selection
27678     *
27679     * @param {Array} sources
27680     *        The sources for a media asset
27681     *
27682     * @return {Object|boolean}
27683     *         Object of source and tech order or false
27684     */
27685    ;
27686
27687    _proto.selectSource = function selectSource(sources) {
27688      var _this13 = this;
27689
27690      // Get only the techs specified in `techOrder` that exist and are supported by the
27691      // current platform
27692      var techs = this.options_.techOrder.map(function (techName) {
27693        return [techName, Tech.getTech(techName)];
27694      }).filter(function (_ref) {
27695        var techName = _ref[0],
27696            tech = _ref[1];
27697
27698        // Check if the current tech is defined before continuing
27699        if (tech) {
27700          // Check if the browser supports this technology
27701          return tech.isSupported();
27702        }
27703
27704        log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
27705        return false;
27706      }); // Iterate over each `innerArray` element once per `outerArray` element and execute
27707      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
27708      // that value.
27709
27710      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
27711        var found;
27712        outerArray.some(function (outerChoice) {
27713          return innerArray.some(function (innerChoice) {
27714            found = tester(outerChoice, innerChoice);
27715
27716            if (found) {
27717              return true;
27718            }
27719          });
27720        });
27721        return found;
27722      };
27723
27724      var foundSourceAndTech;
27725
27726      var flip = function flip(fn) {
27727        return function (a, b) {
27728          return fn(b, a);
27729        };
27730      };
27731
27732      var finder = function finder(_ref2, source) {
27733        var techName = _ref2[0],
27734            tech = _ref2[1];
27735
27736        if (tech.canPlaySource(source, _this13.options_[techName.toLowerCase()])) {
27737          return {
27738            source: source,
27739            tech: techName
27740          };
27741        }
27742      }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
27743      // to select from them based on their priority.
27744
27745
27746      if (this.options_.sourceOrder) {
27747        // Source-first ordering
27748        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
27749      } else {
27750        // Tech-first ordering
27751        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
27752      }
27753
27754      return foundSourceAndTech || false;
27755    }
27756    /**
27757     * Executes source setting and getting logic
27758     *
27759     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
27760     *        A SourceObject, an array of SourceObjects, or a string referencing
27761     *        a URL to a media source. It is _highly recommended_ that an object
27762     *        or array of objects is used here, so that source selection
27763     *        algorithms can take the `type` into account.
27764     *
27765     *        If not provided, this method acts as a getter.
27766     * @param {boolean} isRetry
27767     *        Indicates whether this is being called internally as a result of a retry
27768     *
27769     * @return {string|undefined}
27770     *         If the `source` argument is missing, returns the current source
27771     *         URL. Otherwise, returns nothing/undefined.
27772     */
27773    ;
27774
27775    _proto.handleSrc_ = function handleSrc_(source, isRetry) {
27776      var _this14 = this;
27777
27778      // getter usage
27779      if (typeof source === 'undefined') {
27780        return this.cache_.src || '';
27781      } // Reset retry behavior for new source
27782
27783
27784      if (this.resetRetryOnError_) {
27785        this.resetRetryOnError_();
27786      } // filter out invalid sources and turn our source into
27787      // an array of source objects
27788
27789
27790      var sources = filterSource(source); // if a source was passed in then it is invalid because
27791      // it was filtered to a zero length Array. So we have to
27792      // show an error
27793
27794      if (!sources.length) {
27795        this.setTimeout(function () {
27796          this.error({
27797            code: 4,
27798            message: this.localize(this.options_.notSupportedMessage)
27799          });
27800        }, 0);
27801        return;
27802      } // initial sources
27803
27804
27805      this.changingSrc_ = true; // Only update the cached source list if we are not retrying a new source after error,
27806      // since in that case we want to include the failed source(s) in the cache
27807
27808      if (!isRetry) {
27809        this.cache_.sources = sources;
27810      }
27811
27812      this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
27813
27814      setSource(this, sources[0], function (middlewareSource, mws) {
27815        _this14.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
27816        // the source that is selected could be out of order from the cache update above this callback.
27817
27818        if (!isRetry) {
27819          _this14.cache_.sources = sources;
27820        }
27821
27822        _this14.updateSourceCaches_(middlewareSource);
27823
27824        var err = _this14.src_(middlewareSource);
27825
27826        if (err) {
27827          if (sources.length > 1) {
27828            return _this14.handleSrc_(sources.slice(1));
27829          }
27830
27831          _this14.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
27832
27833          _this14.setTimeout(function () {
27834            this.error({
27835              code: 4,
27836              message: this.localize(this.options_.notSupportedMessage)
27837            });
27838          }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
27839          // this needs a better comment about why this is needed
27840
27841
27842          _this14.triggerReady();
27843
27844          return;
27845        }
27846
27847        setTech(mws, _this14.tech_);
27848      }); // Try another available source if this one fails before playback.
27849
27850      if (this.options_.retryOnError && sources.length > 1) {
27851        var retry = function retry() {
27852          // Remove the error modal
27853          _this14.error(null);
27854
27855          _this14.handleSrc_(sources.slice(1), true);
27856        };
27857
27858        var stopListeningForErrors = function stopListeningForErrors() {
27859          _this14.off('error', retry);
27860        };
27861
27862        this.one('error', retry);
27863        this.one('playing', stopListeningForErrors);
27864
27865        this.resetRetryOnError_ = function () {
27866          _this14.off('error', retry);
27867
27868          _this14.off('playing', stopListeningForErrors);
27869        };
27870      }
27871    }
27872    /**
27873     * Get or set the video source.
27874     *
27875     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
27876     *        A SourceObject, an array of SourceObjects, or a string referencing
27877     *        a URL to a media source. It is _highly recommended_ that an object
27878     *        or array of objects is used here, so that source selection
27879     *        algorithms can take the `type` into account.
27880     *
27881     *        If not provided, this method acts as a getter.
27882     *
27883     * @return {string|undefined}
27884     *         If the `source` argument is missing, returns the current source
27885     *         URL. Otherwise, returns nothing/undefined.
27886     */
27887    ;
27888
27889    _proto.src = function src(source) {
27890      return this.handleSrc_(source, false);
27891    }
27892    /**
27893     * Set the source object on the tech, returns a boolean that indicates whether
27894     * there is a tech that can play the source or not
27895     *
27896     * @param {Tech~SourceObject} source
27897     *        The source object to set on the Tech
27898     *
27899     * @return {boolean}
27900     *         - True if there is no Tech to playback this source
27901     *         - False otherwise
27902     *
27903     * @private
27904     */
27905    ;
27906
27907    _proto.src_ = function src_(source) {
27908      var _this15 = this;
27909
27910      var sourceTech = this.selectSource([source]);
27911
27912      if (!sourceTech) {
27913        return true;
27914      }
27915
27916      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
27917        this.changingSrc_ = true; // load this technology with the chosen source
27918
27919        this.loadTech_(sourceTech.tech, sourceTech.source);
27920        this.tech_.ready(function () {
27921          _this15.changingSrc_ = false;
27922        });
27923        return false;
27924      } // wait until the tech is ready to set the source
27925      // and set it synchronously if possible (#2326)
27926
27927
27928      this.ready(function () {
27929        // The setSource tech method was added with source handlers
27930        // so older techs won't support it
27931        // We need to check the direct prototype for the case where subclasses
27932        // of the tech do not support source handlers
27933        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
27934          this.techCall_('setSource', source);
27935        } else {
27936          this.techCall_('src', source.src);
27937        }
27938
27939        this.changingSrc_ = false;
27940      }, true);
27941      return false;
27942    }
27943    /**
27944     * Begin loading the src data.
27945     */
27946    ;
27947
27948    _proto.load = function load() {
27949      this.techCall_('load');
27950    }
27951    /**
27952     * Reset the player. Loads the first tech in the techOrder,
27953     * removes all the text tracks in the existing `tech`,
27954     * and calls `reset` on the `tech`.
27955     */
27956    ;
27957
27958    _proto.reset = function reset() {
27959      var _this16 = this;
27960
27961      var PromiseClass = this.options_.Promise || window_1.Promise;
27962
27963      if (this.paused() || !PromiseClass) {
27964        this.doReset_();
27965      } else {
27966        var playPromise = this.play();
27967        silencePromise(playPromise.then(function () {
27968          return _this16.doReset_();
27969        }));
27970      }
27971    };
27972
27973    _proto.doReset_ = function doReset_() {
27974      if (this.tech_) {
27975        this.tech_.clearTracks('text');
27976      }
27977
27978      this.resetCache_();
27979      this.poster('');
27980      this.loadTech_(this.options_.techOrder[0], null);
27981      this.techCall_('reset');
27982      this.resetControlBarUI_();
27983
27984      if (isEvented(this)) {
27985        this.trigger('playerreset');
27986      }
27987    }
27988    /**
27989     * Reset Control Bar's UI by calling sub-methods that reset
27990     * all of Control Bar's components
27991     */
27992    ;
27993
27994    _proto.resetControlBarUI_ = function resetControlBarUI_() {
27995      this.resetProgressBar_();
27996      this.resetPlaybackRate_();
27997      this.resetVolumeBar_();
27998    }
27999    /**
28000     * Reset tech's progress so progress bar is reset in the UI
28001     */
28002    ;
28003
28004    _proto.resetProgressBar_ = function resetProgressBar_() {
28005      this.currentTime(0);
28006      var _this$controlBar = this.controlBar,
28007          durationDisplay = _this$controlBar.durationDisplay,
28008          remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
28009
28010      if (durationDisplay) {
28011        durationDisplay.updateContent();
28012      }
28013
28014      if (remainingTimeDisplay) {
28015        remainingTimeDisplay.updateContent();
28016      }
28017    }
28018    /**
28019     * Reset Playback ratio
28020     */
28021    ;
28022
28023    _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
28024      this.playbackRate(this.defaultPlaybackRate());
28025      this.handleTechRateChange_();
28026    }
28027    /**
28028     * Reset Volume bar
28029     */
28030    ;
28031
28032    _proto.resetVolumeBar_ = function resetVolumeBar_() {
28033      this.volume(1.0);
28034      this.trigger('volumechange');
28035    }
28036    /**
28037     * Returns all of the current source objects.
28038     *
28039     * @return {Tech~SourceObject[]}
28040     *         The current source objects
28041     */
28042    ;
28043
28044    _proto.currentSources = function currentSources() {
28045      var source = this.currentSource();
28046      var sources = []; // assume `{}` or `{ src }`
28047
28048      if (Object.keys(source).length !== 0) {
28049        sources.push(source);
28050      }
28051
28052      return this.cache_.sources || sources;
28053    }
28054    /**
28055     * Returns the current source object.
28056     *
28057     * @return {Tech~SourceObject}
28058     *         The current source object
28059     */
28060    ;
28061
28062    _proto.currentSource = function currentSource() {
28063      return this.cache_.source || {};
28064    }
28065    /**
28066     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
28067     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
28068     *
28069     * @return {string}
28070     *         The current source
28071     */
28072    ;
28073
28074    _proto.currentSrc = function currentSrc() {
28075      return this.currentSource() && this.currentSource().src || '';
28076    }
28077    /**
28078     * Get the current source type e.g. video/mp4
28079     * This can allow you rebuild the current source object so that you could load the same
28080     * source and tech later
28081     *
28082     * @return {string}
28083     *         The source MIME type
28084     */
28085    ;
28086
28087    _proto.currentType = function currentType() {
28088      return this.currentSource() && this.currentSource().type || '';
28089    }
28090    /**
28091     * Get or set the preload attribute
28092     *
28093     * @param {boolean} [value]
28094     *        - true means that we should preload
28095     *        - false means that we should not preload
28096     *
28097     * @return {string}
28098     *         The preload attribute value when getting
28099     */
28100    ;
28101
28102    _proto.preload = function preload(value) {
28103      if (value !== undefined) {
28104        this.techCall_('setPreload', value);
28105        this.options_.preload = value;
28106        return;
28107      }
28108
28109      return this.techGet_('preload');
28110    }
28111    /**
28112     * Get or set the autoplay option. When this is a boolean it will
28113     * modify the attribute on the tech. When this is a string the attribute on
28114     * the tech will be removed and `Player` will handle autoplay on loadstarts.
28115     *
28116     * @param {boolean|string} [value]
28117     *        - true: autoplay using the browser behavior
28118     *        - false: do not autoplay
28119     *        - 'play': call play() on every loadstart
28120     *        - 'muted': call muted() then play() on every loadstart
28121     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
28122     *        - *: values other than those listed here will be set `autoplay` to true
28123     *
28124     * @return {boolean|string}
28125     *         The current value of autoplay when getting
28126     */
28127    ;
28128
28129    _proto.autoplay = function autoplay(value) {
28130      // getter usage
28131      if (value === undefined) {
28132        return this.options_.autoplay || false;
28133      }
28134
28135      var techAutoplay; // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
28136
28137      if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
28138        this.options_.autoplay = value;
28139        this.manualAutoplay_(typeof value === 'string' ? value : 'play');
28140        techAutoplay = false;
28140 // any falsy value sets autoplay to false in the browser,
28141        // lets do the same
28142      } else if (!value) {
28143        this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
28144      } else {
28145        this.options_.autoplay = true;
28146      }
28147
28148      techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
28149      // a setAutoplay call on tech ready. We do this because the
28150      // autoplay option will be passed in the constructor and we
28151      // do not need to set it twice
28152
28153      if (this.tech_) {
28154        this.techCall_('setAutoplay', techAutoplay);
28155      }
28156    }
28157    /**
28158     * Set or unset the playsinline attribute.
28159     * Playsinline tells the browser that non-fullscreen playback is preferred.
28160     *
28161     * @param {boolean} [value]
28162     *        - true means that we should try to play inline by default
28163     *        - false means that we should use the browser's default playback mode,
28164     *          which in most cases is inline. iOS Safari is a notable exception
28165     *          and plays fullscreen by default.
28166     *
28167     * @return {string|Player}
28168     *         - the current value of playsinline
28169     *         - the player when setting
28170     *
28171     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
28172     */
28173    ;
28174
28175    _proto.playsinline = function playsinline(value) {
28176      if (value !== undefined) {
28177        this.techCall_('setPlaysinline', value);
28178        this.options_.playsinline = value;
28179        return this;
28180      }
28181
28182      return this.techGet_('playsinline');
28183    }
28184    /**
28185     * Get or set the loop attribute on the video element.
28186     *
28187     * @param {boolean} [value]
28188     *        - true means that we should loop the video
28189     *        - false means that we should not loop the video
28190     *
28191     * @return {boolean}
28192     *         The current value of loop when getting
28193     */
28194    ;
28195
28196    _proto.loop = function loop(value) {
28197      if (value !== undefined) {
28198        this.techCall_('setLoop', value);
28199        this.options_.loop = value;
28200        return;
28201      }
28202
28203      return this.techGet_('loop');
28204    }
28205    /**
28206     * Get or set the poster image source url
28207     *
28208     * @fires Player#posterchange
28209     *
28210     * @param {string} [src]
28211     *        Poster image source URL
28212     *
28213     * @return {string}
28214     *         The current value of poster when getting
28215     */
28216    ;
28217
28218    _proto.poster = function poster(src) {
28219      if (src === undefined) {
28220        return this.poster_;
28221      } // The correct way to remove a poster is to set as an empty string
28222      // other falsey values will throw errors
28223
28224
28225      if (!src) {
28226        src = '';
28227      }
28228
28229      if (src === this.poster_) {
28230        return;
28231      } // update the internal poster variable
28232
28233
28234      this.poster_ = src; // update the tech's poster
28235
28236      this.techCall_('setPoster', src);
28237      this.isPosterFromTech_ = false; // alert components that the poster has been set
28238
28239      /**
28240       * This event fires when the poster image is changed on the player.
28241       *
28242       * @event Player#posterchange
28243       * @type {EventTarget~Event}
28244       */
28245
28246      this.trigger('posterchange');
28247    }
28248    /**
28249     * Some techs (e.g. YouTube) can provide a poster source in an
28250     * asynchronous way. We want the poster component to use this
28251     * poster source so that it covers up the tech's controls.
28252     * (YouTube's play button). However we only want to use this
28253     * source if the player user hasn't set a poster through
28254     * the normal APIs.
28255     *
28256     * @fires Player#posterchange
28257     * @listens Tech#posterchange
28258     * @private
28259     */
28260    ;
28261
28262    _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
28263      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
28264        var newPoster = this.tech_.poster() || '';
28265
28266        if (newPoster !== this.poster_) {
28267          this.poster_ = newPoster;
28268          this.isPosterFromTech_ = true; // Let components know the poster has changed
28269
28270          this.trigger('posterchange');
28271        }
28272      }
28273    }
28274    /**
28275     * Get or set whether or not the controls are showing.
28276     *
28277     * @fires Player#controlsenabled
28278     *
28279     * @param {boolean} [bool]
28280     *        - true to turn controls on
28281     *        - false to turn controls off
28282     *
28283     * @return {boolean}
28284     *         The current value of controls when getting
28285     */
28286    ;
28287
28288    _proto.controls = function controls(bool) {
28289      if (bool === undefined) {
28290        return !!this.controls_;
28291      }
28292
28293      bool = !!bool; // Don't trigger a change event unless it actually changed
28294
28295      if (this.controls_ === bool) {
28296        return;
28297      }
28298
28299      this.controls_ = bool;
28300
28301      if (this.usingNativeControls()) {
28302        this.techCall_('setControls', bool);
28303      }
28304
28305      if (this.controls_) {
28306        this.removeClass('vjs-controls-disabled');
28307        this.addClass('vjs-controls-enabled');
28308        /**
28309         * @event Player#controlsenabled
28310         * @type {EventTarget~Event}
28311         */
28312
28313        this.trigger('controlsenabled');
28314
28315        if (!this.usingNativeControls()) {
28316          this.addTechControlsListeners_();
28317        }
28318      } else {
28319        this.removeClass('vjs-controls-enabled');
28320        this.addClass('vjs-controls-disabled');
28321        /**
28322         * @event Player#controlsdisabled
28323         * @type {EventTarget~Event}
28324         */
28325
28326        this.trigger('controlsdisabled');
28327
28328        if (!this.usingNativeControls()) {
28329          this.removeTechControlsListeners_();
28330        }
28331      }
28332    }
28333    /**
28334     * Toggle native controls on/off. Native controls are the controls built into
28335     * devices (e.g. default iPhone controls) or other techs
28336     * (e.g. Vimeo Controls)
28337     * **This should only be set by the current tech, because only the tech knows
28338     * if it can support native controls**
28339     *
28340     * @fires Player#usingnativecontrols
28341     * @fires Player#usingcustomcontrols
28342     *
28343     * @param {boolean} [bool]
28344     *        - true to turn native controls on
28345     *        - false to turn native controls off
28346     *
28347     * @return {boolean}
28348     *         The current value of native controls when getting
28349     */
28350    ;
28351
28352    _proto.usingNativeControls = function usingNativeControls(bool) {
28353      if (bool === undefined) {
28354        return !!this.usingNativeControls_;
28355      }
28356
28357      bool = !!bool; // Don't trigger a change event unless it actually changed
28358
28359      if (this.usingNativeControls_ === bool) {
28360        return;
28361      }
28362
28363      this.usingNativeControls_ = bool;
28364
28365      if (this.usingNativeControls_) {
28366        this.addClass('vjs-using-native-controls');
28367        /**
28368         * player is using the native device controls
28369         *
28370         * @event Player#usingnativecontrols
28371         * @type {EventTarget~Event}
28372         */
28373
28374        this.trigger('usingnativecontrols');
28375      } else {
28376        this.removeClass('vjs-using-native-controls');
28377        /**
28378         * player is using the custom HTML controls
28379         *
28380         * @event Player#usingcustomcontrols
28381         * @type {EventTarget~Event}
28382         */
28383
28384        this.trigger('usingcustomcontrols');
28385      }
28386    }
28387    /**
28388     * Set or get the current MediaError
28389     *
28390     * @fires Player#error
28391     *
28392     * @param  {MediaError|string|number} [err]
28393     *         A MediaError or a string/number to be turned
28394     *         into a MediaError
28395     *
28396     * @return {MediaError|null}
28397     *         The current MediaError when getting (or null)
28398     */
28399    ;
28400
28401    _proto.error = function error(err) {
28402      var _this17 = this;
28403
28404      if (err === undefined) {
28405        return this.error_ || null;
28406      } // allow hooks to modify error object
28407
28408
28409      hooks('beforeerror').forEach(function (hookFunction) {
28410        var newErr = hookFunction(_this17, err);
28411
28412        if (!(isObject$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
28413          _this17.log.error('please return a value that MediaError expects in beforeerror hooks');
28414
28415          return;
28416        }
28417
28418        err = newErr;
28419      }); // Suppress the first error message for no compatible source until
28420      // user interaction
28421
28422      if (this.options_.suppressNotSupportedError && err && err.code === 4) {
28423        var triggerSuppressedError = function triggerSuppressedError() {
28424          this.error(err);
28425        };
28426
28427        this.options_.suppressNotSupportedError = false;
28428        this.any(['click', 'touchstart'], triggerSuppressedError);
28429        this.one('loadstart', function () {
28430          this.off(['click', 'touchstart'], triggerSuppressedError);
28431        });
28432        return;
28433      } // restoring to default
28434
28435
28436      if (err === null) {
28437        this.error_ = err;
28438        this.removeClass('vjs-error');
28439
28440        if (this.errorDisplay) {
28441          this.errorDisplay.close();
28442        }
28443
28444        return;
28445      }
28446      
28447      this.error_ = new MediaError(err); // add the vjs-error classname to the player
28448
28449      if(this.error_.code == '2') {
28450	
28451        var endEl = this.el_.parentNode.parentNode.querySelector('.video__guide.video__end');
28452        if(endEl && endEl.getAttribute('style')) {
28453          var confirmEl = this.el_.parentNode.parentNode.querySelector('.video__guide.video__data-use');
28454          confirmEl.setAttribute('style','display:none');	
28455        
28456          var refreshEl = this.el_.parentNode.parentNode.querySelector('#video-player-reload');
28457          refreshEl.removeAttribute('style');
28458          if(initMotion) initMotion();
28459          //this.el_.parentNode.removeChild(this.el_);
28460        }
28461        
28462        log$1.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
28463        return;
28464              
28465      } else {
28466      
28467          this.addClass('vjs-error'); // log the name of the error type and any message
28468          // IE11 logs "[object object]" and required you to expand message to see error object
28469
28470          log$1.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
28471      }
28472      
28473      /**
28474       * @event Player#error
28475       * @type {EventTarget~Event}
28476       */
28477
28478      this.trigger('error'); // notify hooks of the per player error
28479
28480      hooks('error').forEach(function (hookFunction) {
28481        return hookFunction(_this17, _this17.error_);
28482      });
28483      return;
28484    }
28485    /**
28486     * Report user activity
28487     *
28488     * @param {Object} event
28489     *        Event object
28490     */
28491    ;
28492
28493    _proto.reportUserActivity = function reportUserActivity(event) {
28494      this.userActivity_ = true;
28495    }
28496    /**
28497     * Get/set if user is active
28498     *
28499     * @fires Player#useractive
28500     * @fires Player#userinactive
28501     *
28502     * @param {boolean} [bool]
28503     *        - true if the user is active
28504     *        - false if the user is inactive
28505     *
28506     * @return {boolean}
28507     *         The current value of userActive when getting
28508     */
28509    ;
28510
28511    _proto.userActive = function userActive(bool) {
28512      if (bool === undefined) {
28513        return this.userActive_;
28514      }
28515
28516      bool = !!bool;
28517
28518      if (bool === this.userActive_) {
28519        return;
28520      }
28521
28522      this.userActive_ = bool;
28523
28524      if (this.userActive_) {
28525        this.userActivity_ = true;
28526        this.removeClass('vjs-user-inactive');
28527        this.addClass('vjs-user-active');
28528        /**
28529         * @event Player#useractive
28530         * @type {EventTarget~Event}
28531         */
28532
28533        this.trigger('useractive');
28534        return;
28535      } // Chrome/Safari/IE have bugs where when you change the cursor it can
28536      // trigger a mousemove event. This causes an issue when you're hiding
28537      // the cursor when the user is inactive, and a mousemove signals user
28538      // activity. Making it impossible to go into inactive mode. Specifically
28539      // this happens in fullscreen when we really need to hide the cursor.
28540      //
28541      // When this gets resolved in ALL browsers it can be removed
28542      // https://code.google.com/p/chromium/issues/detail?id=103041
28543
28544
28545      if (this.tech_) {
28546        this.tech_.one('mousemove', function (e) {
28547          e.stopPropagation();
28548          e.preventDefault();
28549        });
28550      }
28551
28552      this.userActivity_ = false;
28553      this.removeClass('vjs-user-active');
28554      this.addClass('vjs-user-inactive');
28555      /**
28556       * @event Player#userinactive
28557       * @type {EventTarget~Event}
28558       */
28559
28560      this.trigger('userinactive');
28561    }
28562    /**
28563     * Listen for user activity based on timeout value
28564     *
28565     * @private
28566     */
28567    ;
28568
28569    _proto.listenForUserActivity_ = function listenForUserActivity_() {
28570      var mouseInProgress;
28571      var lastMoveX;
28572      var lastMoveY;
28573      var handleActivity = bind(this, this.reportUserActivity);
28574
28575      var handleMouseMove = function handleMouseMove(e) {
28576        // #1068 - Prevent mousemove spamming
28577        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
28578        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
28579          lastMoveX = e.screenX;
28580          lastMoveY = e.screenY;
28581          handleActivity();
28582        }
28583      };
28584
28585      var handleMouseDown = function handleMouseDown() {
28586        handleActivity(); // For as long as the they are touching the device or have their mouse down,
28587        // we consider them active even if they're not moving their finger or mouse.
28588        // So we want to continue to update that they are active
28589
28590        this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
28591        // as the activityCheck interval (250) should ensure we never miss the
28592        // next activityCheck
28593
28594        mouseInProgress = this.setInterval(handleActivity, 250);
28595      };
28596
28597      var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) {
28598        handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
28599
28600        this.clearInterval(mouseInProgress);
28601      }; // Any mouse movement will be considered user activity
28602
28603
28604      this.on('mousedown', handleMouseDown);
28605      this.on('mousemove', handleMouseMove);
28606      this.on('mouseup', handleMouseUpAndMouseLeave);
28607      this.on('mouseleave', handleMouseUpAndMouseLeave);
28608      var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
28609      // controlBar would no longer be hidden by default timeout.
28610
28611      if (controlBar && !IS_IOS && !IS_ANDROID) {
28612        controlBar.on('mouseenter', function (event) {
28613          if (this.player().options_.inactivityTimeout !== 0) {
28614            this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
28615          }
28616
28617          this.player().options_.inactivityTimeout = 0;
28618        });
28619        controlBar.on('mouseleave', function (event) {
28620          this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
28621        });
28622      } // Listen for keyboard navigation
28623      // Shouldn't need to use inProgress interval because of key repeat
28624
28625
28626      this.on('keydown', handleActivity);
28627      this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
28628      // the mousemove/touchmove function itself, to prevent performance degradation.
28629      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
28630      // then gets picked up by this loop
28631      // http://ejohn.org/blog/learning-from-twitter/
28632
28633      var inactivityTimeout;
28634      this.setInterval(function () {
28635        // Check to see if mouse/touch activity has happened
28636        if (!this.userActivity_) {
28637          return;
28638        } // Reset the activity tracker
28639
28640
28641        this.userActivity_ = false; // If the user state was inactive, set the state to active
28642
28643        this.userActive(true); // Clear any existing inactivity timeout to start the timer over
28644
28645        this.clearTimeout(inactivityTimeout);
28646        var timeout = this.options_.inactivityTimeout;
28647
28648        if (timeout <= 0) {
28649          return;
28650        } // In <timeout> milliseconds, if no more activity has occurred the
28651        // user will be considered inactive
28652
28653
28654        inactivityTimeout = this.setTimeout(function () {
28655          // Protect against the case where the inactivityTimeout can trigger just
28656          // before the next user activity is picked up by the activity check loop
28657          // causing a flicker
28658          if (!this.userActivity_) {
28659            this.userActive(false);
28660          }
28661        }, timeout);
28662      }, 250);
28663    }
28664    /**
28665     * Gets or sets the current playback rate. A playback rate of
28666     * 1.0 represents normal speed and 0.5 would indicate half-speed
28667     * playback, for instance.
28668     *
28669     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
28670     *
28671     * @param {number} [rate]
28672     *       New playback rate to set.
28673     *
28674     * @return {number}
28675     *         The current playback rate when getting or 1.0
28676     */
28677    ;
28678
28679    _proto.playbackRate = function playbackRate(rate) {
28680      if (rate !== undefined) {
28681        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
28682        // that is registered above
28683        this.techCall_('setPlaybackRate', rate);
28684        return;
28685      }
28686
28687      if (this.tech_ && this.tech_.featuresPlaybackRate) {
28688        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
28689      }
28690
28691      return 1.0;
28692    }
28693    /**
28694     * Gets or sets the current default playback rate. A default playback rate of
28695     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
28696     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
28697     * not the current playbackRate.
28698     *
28699     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
28700     *
28701     * @param {number} [rate]
28702     *       New default playback rate to set.
28703     *
28704     * @return {number|Player}
28705     *         - The default playback rate when getting or 1.0
28706     *         - the player when setting
28707     */
28708    ;
28709
28710    _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
28711      if (rate !== undefined) {
28712        return this.techCall_('setDefaultPlaybackRate', rate);
28713      }
28714
28715      if (this.tech_ && this.tech_.featuresPlaybackRate) {
28716        return this.techGet_('defaultPlaybackRate');
28717      }
28718
28719      return 1.0;
28720    }
28721    /**
28722     * Gets or sets the audio flag
28723     *
28724     * @param {boolean} bool
28725     *        - true signals that this is an audio player
28726     *        - false signals that this is not an audio player
28727     *
28728     * @return {boolean}
28729     *         The current value of isAudio when getting
28730     */
28731    ;
28732
28733    _proto.isAudio = function isAudio(bool) {
28734      if (bool !== undefined) {
28735        this.isAudio_ = !!bool;
28736        return;
28737      }
28738
28739      return !!this.isAudio_;
28740    }
28741    /**
28742     * A helper method for adding a {@link TextTrack} to our
28743     * {@link TextTrackList}.
28744     *
28745     * In addition to the W3C settings we allow adding additional info through options.
28746     *
28747     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
28748     *
28749     * @param {string} [kind]
28750     *        the kind of TextTrack you are adding
28751     *
28752     * @param {string} [label]
28753     *        the label to give the TextTrack label
28754     *
28755     * @param {string} [language]
28756     *        the language to set on the TextTrack
28757     *
28758     * @return {TextTrack|undefined}
28759     *         the TextTrack that was added or undefined
28760     *         if there is no tech
28761     */
28762    ;
28763
28764    _proto.addTextTrack = function addTextTrack(kind, label, language) {
28765      if (this.tech_) {
28766        return this.tech_.addTextTrack(kind, label, language);
28767      }
28768    }
28769    /**
28770     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
28771     * When manualCleanup is set to false, the track will be automatically removed
28772     * on source changes.
28773     *
28774     * @param {Object} options
28775     *        Options to pass to {@link HTMLTrackElement} during creation. See
28776     *        {@link HTMLTrackElement} for object properties that you should use.
28777     *
28778     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
28779     *                                       removed on a source change
28780     *
28781     * @return {HtmlTrackElement}
28782     *         the HTMLTrackElement that was created and added
28783     *         to the HtmlTrackElementList and the remote
28784     *         TextTrackList
28785     *
28786     * @deprecated The default value of the "manualCleanup" parameter will default
28787     *             to "false" in upcoming versions of Video.js
28788     */
28789    ;
28790
28791    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
28792      if (this.tech_) {
28793        return this.tech_.addRemoteTextTrack(options, manualCleanup);
28794      }
28795    }
28796    /**
28797     * Remove a remote {@link TextTrack} from the respective
28798     * {@link TextTrackList} and {@link HtmlTrackElementList}.
28799     *
28800     * @param {Object} track
28801     *        Remote {@link TextTrack} to remove
28802     *
28803     * @return {undefined}
28804     *         does not return anything
28805     */
28806    ;
28807
28808    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
28809      if (obj === void 0) {
28810        obj = {};
28811      }
28812
28813      var _obj = obj,
28814          track = _obj.track;
28815
28816      if (!track) {
28817        track = obj;
28818      } // destructure the input into an object with a track argument, defaulting to arguments[0]
28819      // default the whole argument to an empty object if nothing was passed in
28820
28821
28822      if (this.tech_) {
28823        return this.tech_.removeRemoteTextTrack(track);
28824      }
28825    }
28826    /**
28827     * Gets available media playback quality metrics as specified by the W3C's Media
28828     * Playback Quality API.
28829     *
28830     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
28831     *
28832     * @return {Object|undefined}
28833     *         An object with supported media playback quality metrics or undefined if there
28834     *         is no tech or the tech does not support it.
28835     */
28836    ;
28837
28838    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
28839      return this.techGet_('getVideoPlaybackQuality');
28840    }
28841    /**
28842     * Get video width
28843     *
28844     * @return {number}
28845     *         current video width
28846     */
28847    ;
28848
28849    _proto.videoWidth = function videoWidth() {
28850      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
28851    }
28852    /**
28853     * Get video height
28854     *
28855     * @return {number}
28856     *         current video height
28857     */
28858    ;
28859
28860    _proto.videoHeight = function videoHeight() {
28861      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
28862    }
28863    /**
28864     * The player's language code.
28865     *
28866     * Changing the langauge will trigger
28867     * [languagechange]{@link Player#event:languagechange}
28868     * which Components can use to update control text.
28869     * ClickableComponent will update its control text by default on
28870     * [languagechange]{@link Player#event:languagechange}.
28871     *
28872     * @fires Player#languagechange
28873     *
28874     * @param {string} [code]
28875     *        the language code to set the player to
28876     *
28877     * @return {string}
28878     *         The current language code when getting
28879     */
28880    ;
28881
28882    _proto.language = function language(code) {
28883      if (code === undefined) {
28884        return this.language_;
28885      }
28886
28887      if (this.language_ !== String(code).toLowerCase()) {
28888        this.language_ = String(code).toLowerCase(); // during first init, it's possible some things won't be evented
28889
28890        if (isEvented(this)) {
28891          /**
28892          * fires when the player language change
28893          *
28894          * @event Player#languagechange
28895          * @type {EventTarget~Event}
28896          */
28897          this.trigger('languagechange');
28898        }
28899      }
28900    }
28901    /**
28902     * Get the player's language dictionary
28903     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
28904     * Languages specified directly in the player options have precedence
28905     *
28906     * @return {Array}
28907     *         An array of of supported languages
28908     */
28909    ;
28910
28911    _proto.languages = function languages() {
28912      return mergeOptions$3(Player.prototype.options_.languages, this.languages_);
28913    }
28914    /**
28915     * returns a JavaScript object reperesenting the current track
28916     * information. **DOES not return it as JSON**
28917     *
28918     * @return {Object}
28919     *         Object representing the current of track info
28920     */
28921    ;
28922
28923    _proto.toJSON = function toJSON() {
28924      var options = mergeOptions$3(this.options_);
28925      var tracks = options.tracks;
28926      options.tracks = [];
28927
28928      for (var i = 0; i < tracks.length; i++) {
28929        var track = tracks[i]; // deep merge tracks and null out player so no circular references
28930
28931        track = mergeOptions$3(track);
28932        track.player = undefined;
28933        options.tracks[i] = track;
28934      }
28935
28936      return options;
28937    }
28938    /**
28939     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
28940     * component) that immediately overlays the player with arbitrary
28941     * content and removes itself when closed.
28942     *
28943     * @param {string|Function|Element|Array|null} content
28944     *        Same as {@link ModalDialog#content}'s param of the same name.
28945     *        The most straight-forward usage is to provide a string or DOM
28946     *        element.
28947     *
28948     * @param {Object} [options]
28949     *        Extra options which will be passed on to the {@link ModalDialog}.
28950     *
28951     * @return {ModalDialog}
28952     *         the {@link ModalDialog} that was created
28953     */
28954    ;
28955
28956    _proto.createModal = function createModal(content, options) {
28957      var _this18 = this;
28958
28959      options = options || {};
28960      options.content = content || '';
28961      var modal = new ModalDialog(this, options);
28962      this.addChild(modal);
28963      modal.on('dispose', function () {
28964        _this18.removeChild(modal);
28965      });
28966      modal.open();
28967      return modal;
28968    }
28969    /**
28970     * Change breakpoint classes when the player resizes.
28971     *
28972     * @private
28973     */
28974    ;
28975
28976    _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
28977      if (!this.responsive()) {
28978        return;
28979      }
28980
28981      var currentBreakpoint = this.currentBreakpoint();
28982      var currentWidth = this.currentWidth();
28983
28984      for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
28985        var candidateBreakpoint = BREAKPOINT_ORDER[i];
28986        var maxWidth = this.breakpoints_[candidateBreakpoint];
28987
28988        if (currentWidth <= maxWidth) {
28989          // The current breakpoint did not change, nothing to do.
28990          if (currentBreakpoint === candidateBreakpoint) {
28991            return;
28992          } // Only remove a class if there is a current breakpoint.
28993
28994
28995          if (currentBreakpoint) {
28996            this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
28997          }
28998
28999          this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
29000          this.breakpoint_ = candidateBreakpoint;
29001          break;
29002        }
29003      }
29004    }
29005    /**
29006     * Removes the current breakpoint.
29007     *
29008     * @private
29009     */
29010    ;
29011
29012    _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
29013      var className = this.currentBreakpointClass();
29014      this.breakpoint_ = '';
29015
29016      if (className) {
29017        this.removeClass(className);
29018      }
29019    }
29020    /**
29021     * Get or set breakpoints on the player.
29022     *
29023     * Calling this method with an object or `true` will remove any previous
29024     * custom breakpoints and start from the defaults again.
29025     *
29026     * @param  {Object|boolean} [breakpoints]
29027     *         If an object is given, it can be used to provide custom
29028     *         breakpoints. If `true` is given, will set default breakpoints.
29029     *         If this argument is not given, will simply return the current
29030     *         breakpoints.
29031     *
29032     * @param  {number} [breakpoints.tiny]
29033     *         The maximum width for the "vjs-layout-tiny" class.
29034     *
29035     * @param  {number} [breakpoints.xsmall]
29036     *         The maximum width for the "vjs-layout-x-small" class.
29037     *
29038     * @param  {number} [breakpoints.small]
29039     *         The maximum width for the "vjs-layout-small" class.
29040     *
29041     * @param  {number} [breakpoints.medium]
29042     *         The maximum width for the "vjs-layout-medium" class.
29043     *
29044     * @param  {number} [breakpoints.large]
29045     *         The maximum width for the "vjs-layout-large" class.
29046     *
29047     * @param  {number} [breakpoints.xlarge]
29048     *         The maximum width for the "vjs-layout-x-large" class.
29049     *
29050     * @param  {number} [breakpoints.huge]
29051     *         The maximum width for the "vjs-layout-huge" class.
29052     *
29053     * @return {Object}
29054     *         An object mapping breakpoint names to maximum width values.
29055     */
29056    ;
29057
29058    _proto.breakpoints = function breakpoints(_breakpoints) {
29059      // Used as a getter.
29060      if (_breakpoints === undefined) {
29061        return assign(this.breakpoints_);
29062      }
29063
29064      this.breakpoint_ = '';
29065      this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
29066      // selected breakpoint.
29067
29068      this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
29069
29070      return assign(this.breakpoints_);
29071    }
29072    /**
29073     * Get or set a flag indicating whether or not this player should adjust
29074     * its UI based on its dimensions.
29075     *
29076     * @param  {boolean} value
29077     *         Should be `true` if the player should adjust its UI based on its
29078     *         dimensions; otherwise, should be `false`.
29079     *
29080     * @return {boolean}
29081     *         Will be `true` if this player should adjust its UI based on its
29082     *         dimensions; otherwise, will be `false`.
29083     */
29084    ;
29085
29086    _proto.responsive = function responsive(value) {
29087      // Used as a getter.
29088      if (value === undefined) {
29089        return this.responsive_;
29090      }
29091
29092      value = Boolean(value);
29093      var current = this.responsive_; // Nothing changed.
29094
29095      if (value === current) {
29096        return;
29097      } // The value actually changed, set it.
29098
29099
29100      this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
29101      // player is now responsive.
29102
29103      if (value) {
29104        this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
29105        this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
29106      } else {
29107        this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
29108        this.removeCurrentBreakpoint_();
29109      }
29110
29111      return value;
29112    }
29113    /**
29114     * Get current breakpoint name, if any.
29115     *
29116     * @return {string}
29117     *         If there is currently a breakpoint set, returns a the key from the
29118     *         breakpoints object matching it. Otherwise, returns an empty string.
29119     */
29120    ;
29121
29122    _proto.currentBreakpoint = function currentBreakpoint() {
29123      return this.breakpoint_;
29124    }
29125    /**
29126     * Get the current breakpoint class name.
29127     *
29128     * @return {string}
29129     *         The matching class name (e.g. `"vjs-layout-tiny"` or
29130     *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
29131     *         there is no current breakpoint.
29132     */
29133    ;
29134
29135    _proto.currentBreakpointClass = function currentBreakpointClass() {
29136      return BREAKPOINT_CLASSES[this.breakpoint_] || '';
29137    }
29138    /**
29139     * An object that describes a single piece of media.
29140     *
29141     * Properties that are not part of this type description will be retained; so,
29142     * this can be viewed as a generic metadata storage mechanism as well.
29143     *
29144     * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
29145     * @typedef  {Object} Player~MediaObject
29146     *
29147     * @property {string} [album]
29148     *           Unused, except if this object is passed to the `MediaSession`
29149     *           API.
29150     *
29151     * @property {string} [artist]
29152     *           Unused, except if this object is passed to the `MediaSession`
29153     *           API.
29154     *
29155     * @property {Object[]} [artwork]
29156     *           Unused, except if this object is passed to the `MediaSession`
29157     *           API. If not specified, will be populated via the `poster`, if
29158     *           available.
29159     *
29160     * @property {string} [poster]
29161     *           URL to an image that will display before playback.
29162     *
29163     * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
29164     *           A single source object, an array of source objects, or a string
29165     *           referencing a URL to a media source. It is _highly recommended_
29166     *           that an object or array of objects is used here, so that source
29167     *           selection algorithms can take the `type` into account.
29168     *
29169     * @property {string} [title]
29170     *           Unused, except if this object is passed to the `MediaSession`
29171     *           API.
29172     *
29173     * @property {Object[]} [textTracks]
29174     *           An array of objects to be used to create text tracks, following
29175     *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
29176     *           For ease of removal, these will be created as "remote" text
29177     *           tracks and set to automatically clean up on source changes.
29178     *
29179     *           These objects may have properties like `src`, `kind`, `label`,
29180     *           and `language`, see {@link Tech#createRemoteTextTrack}.
29181     */
29182
29183    /**
29184     * Populate the player using a {@link Player~MediaObject|MediaObject}.
29185     *
29186     * @param  {Player~MediaObject} media
29187     *         A media object.
29188     *
29189     * @param  {Function} ready
29190     *         A callback to be called when the player is ready.
29191     */
29192    ;
29193
29194    _proto.loadMedia = function loadMedia(media, ready) {
29195      var _this19 = this;
29196
29197      if (!media || typeof media !== 'object') {
29198        return;
29199      }
29200
29201      this.reset(); // Clone the media object so it cannot be mutated from outside.
29202
29203      this.cache_.media = mergeOptions$3(media);
29204      var _this$cache_$media = this.cache_.media,
29205          artwork = _this$cache_$media.artwork,
29206          poster = _this$cache_$media.poster,
29207          src = _this$cache_$media.src,
29208          textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
29209
29210      if (!artwork && poster) {
29211        this.cache_.media.artwork = [{
29212          src: poster,
29213          type: getMimetype(poster)
29214        }];
29215      }
29216
29217      if (src) {
29218        this.src(src);
29219      }
29220
29221      if (poster) {
29222        this.poster(poster);
29223      }
29224
29225      if (Array.isArray(textTracks)) {
29226        textTracks.forEach(function (tt) {
29227          return _this19.addRemoteTextTrack(tt, false);
29228        });
29229      }
29230
29231      this.ready(ready);
29232    }
29233    /**
29234     * Get a clone of the current {@link Player~MediaObject} for this player.
29235     *
29236     * If the `loadMedia` method has not been used, will attempt to return a
29237     * {@link Player~MediaObject} based on the current state of the player.
29238     *
29239     * @return {Player~MediaObject}
29240     */
29241    ;
29242
29243    _proto.getMedia = function getMedia() {
29244      if (!this.cache_.media) {
29245        var poster = this.poster();
29246        var src = this.currentSources();
29247        var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
29248          return {
29249            kind: tt.kind,
29250            label: tt.label,
29251            language: tt.language,
29252            src: tt.src
29253          };
29254        });
29255        var media = {
29256          src: src,
29257          textTracks: textTracks
29258        };
29259
29260        if (poster) {
29261          media.poster = poster;
29262          media.artwork = [{
29263            src: media.poster,
29264            type: getMimetype(media.poster)
29265          }];
29266        }
29267
29268        return media;
29269      }
29270
29271      return mergeOptions$3(this.cache_.media);
29272    }
29273    /**
29274     * Gets tag settings
29275     *
29276     * @param {Element} tag
29277     *        The player tag
29278     *
29279     * @return {Object}
29280     *         An object containing all of the settings
29281     *         for a player tag
29282     */
29283    ;
29284
29285    Player.getTagSettings = function getTagSettings(tag) {
29286      var baseOptions = {
29287        sources: [],
29288        tracks: []
29289      };
29290      var tagOptions = getAttributes(tag);
29291      var dataSetup = tagOptions['data-setup'];
29292
29293      if (hasClass(tag, 'vjs-fill')) {
29294        tagOptions.fill = true;
29295      }
29296
29297      if (hasClass(tag, 'vjs-fluid')) {
29298        tagOptions.fluid = true;
29299      } // Check if data-setup attr exists.
29300
29301
29302      if (dataSetup !== null) {
29303        // Parse options JSON
29304        // If empty string, make it a parsable json object.
29305        var _safeParseTuple = tuple(dataSetup || '{}'),
29306            err = _safeParseTuple[0],
29307            data = _safeParseTuple[1];
29308
29309        if (err) {
29310          log$1.error(err);
29311        }
29312
29313        assign(tagOptions, data);
29314      }
29315
29316      assign(baseOptions, tagOptions); // Get tag children settings
29317
29318      if (tag.hasChildNodes()) {
29319        var children = tag.childNodes;
29320
29321        for (var i = 0, j = children.length; i < j; i++) {
29322          var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
29323
29324          var childName = child.nodeName.toLowerCase();
29325
29326          if (childName === 'source') {
29327            baseOptions.sources.push(getAttributes(child));
29328          } else if (childName === 'track') {
29329            baseOptions.tracks.push(getAttributes(child));
29330          }
29331        }
29332      }
29333
29334      return baseOptions;
29335    }
29336    /**
29337     * Determine whether or not flexbox is supported
29338     *
29339     * @return {boolean}
29340     *         - true if flexbox is supported
29341     *         - false if flexbox is not supported
29342     */
29343    ;
29344
29345    _proto.flexNotSupported_ = function flexNotSupported_() {
29346      var elem = document_1.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
29347      // common flex features that we can rely on when checking for flex support.
29348
29349      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
29350      'msFlexOrder' in elem.style);
29351    }
29352    /**
29353     * Set debug mode to enable/disable logs at info level.
29354     *
29355     * @param {boolean} enabled
29356     * @fires Player#debugon
29357     * @fires Player#debugoff
29358     */
29359    ;
29360
29361    _proto.debug = function debug(enabled) {
29362      if (enabled === undefined) {
29363        return this.debugEnabled_;
29364      }
29365
29366      if (enabled) {
29367        this.trigger('debugon');
29368        this.previousLogLevel_ = this.log.level;
29369        this.log.level('debug');
29370        this.debugEnabled_ = true;
29371      } else {
29372        this.trigger('debugoff');
29373        this.log.level(this.previousLogLevel_);
29374        this.previousLogLevel_ = undefined;
29375        this.debugEnabled_ = false;
29376      }
29377    }
29378    /**
29379     * Set or get current playback rates.
29380     * Takes an array and updates the playback rates menu with the new items.
29381     * Pass in an empty array to hide the menu.
29382     * Values other than arrays are ignored.
29383     *
29384     * @fires Player#playbackrateschange
29385     * @param {number[]} newRates
29386     *                   The new rates that the playback rates menu should update to.
29387     *                   An empty array will hide the menu
29388     * @return {number[]} When used as a getter will return the current playback rates
29389     */
29390    ;
29391
29392    _proto.playbackRates = function playbackRates(newRates) {
29393      if (newRates === undefined) {
29394        return this.cache_.playbackRates;
29395      } // ignore any value that isn't an array
29396
29397
29398      if (!Array.isArray(newRates)) {
29399        return;
29400      } // ignore any arrays that don't only contain numbers
29401
29402
29403      if (!newRates.every(function (rate) {
29404        return typeof rate === 'number';
29405      })) {
29406        return;
29407      }
29408
29409      this.cache_.playbackRates = newRates;
29410      /**
29411      * fires when the playback rates in a player are changed
29412      *
29413      * @event Player#playbackrateschange
29414      * @type {EventTarget~Event}
29415      */
29416
29417      this.trigger('playbackrateschange');
29418    };
29419
29420    return Player;
29421  }(Component$1);
29422  /**
29423   * Get the {@link VideoTrackList}
29424   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
29425   *
29426   * @return {VideoTrackList}
29427   *         the current video track list
29428   *
29429   * @method Player.prototype.videoTracks
29430   */
29431
29432  /**
29433   * Get the {@link AudioTrackList}
29434   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
29435   *
29436   * @return {AudioTrackList}
29437   *         the current audio track list
29438   *
29439   * @method Player.prototype.audioTracks
29440   */
29441
29442  /**
29443   * Get the {@link TextTrackList}
29444   *
29445   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
29446   *
29447   * @return {TextTrackList}
29448   *         the current text track list
29449   *
29450   * @method Player.prototype.textTracks
29451   */
29452
29453  /**
29454   * Get the remote {@link TextTrackList}
29455   *
29456   * @return {TextTrackList}
29457   *         The current remote text track list
29458   *
29459   * @method Player.prototype.remoteTextTracks
29460   */
29461
29462  /**
29463   * Get the remote {@link HtmlTrackElementList} tracks.
29464   *
29465   * @return {HtmlTrackElementList}
29466   *         The current remote text track element list
29467   *
29468   * @method Player.prototype.remoteTextTrackEls
29469   */
29470
29471
29472  ALL.names.forEach(function (name) {
29473    var props = ALL[name];
29474
29475    Player.prototype[props.getterName] = function () {
29476      if (this.tech_) {
29477        return this.tech_[props.getterName]();
29478      } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
29479      // these will be passed to the tech during loading
29480
29481
29482      this[props.privateName] = this[props.privateName] || new props.ListClass();
29483      return this[props.privateName];
29484    };
29485  });
29486  /**
29487   * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
29488   * sets the `crossOrigin` property on the `<video>` tag to control the CORS
29489   * behavior.
29490   *
29491   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
29492   *
29493   * @param {string} [value]
29494   *        The value to set the `Player`'s crossorigin to. If an argument is
29495   *        given, must be one of `anonymous` or `use-credentials`.
29496   *
29497   * @return {string|undefined}
29498   *         - The current crossorigin value of the `Player` when getting.
29499   *         - undefined when setting
29500   */
29501
29502  Player.prototype.crossorigin = Player.prototype.crossOrigin;
29503  /**
29504   * Global enumeration of players.
29505   *
29506   * The keys are the player IDs and the values are either the {@link Player}
29507   * instance or `null` for disposed players.
29508   *
29509   * @type {Object}
29510   */
29511
29512  Player.players = {};
29513  var navigator = window_1.navigator;
29514  /*
29515   * Player instance options, surfaced using options
29516   * options = Player.prototype.options_
29517   * Make changes in options, not here.
29518   *
29519   * @type {Object}
29520   * @private
29521   */
29522
29523  Player.prototype.options_ = {
29524    // Default order of fallback technology
29525    techOrder: Tech.defaultTechOrder_,
29526    html5: {},
29527    // default inactivity timeout
29528    inactivityTimeout: 2000,
29529    // default playback rates
29530    playbackRates: [],
29531    // Add playback rate selection by adding rates
29532    // 'playbackRates': [0.5, 1, 1.5, 2],
29533    liveui: false,
29534    // Included control sets
29535    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
29536    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
29537    // locales and their language translations
29538    languages: {},
29539    // Default message to show when a video cannot be played.
29540    notSupportedMessage: 'No compatible source was found for this media.',
29541    normalizeAutoplay: false,
29542    fullscreen: {
29543      options: {
29544        navigationUI: 'hide'
29545      }
29546    },
29547    breakpoints: {},
29548    responsive: false
29549  };
29550  [
29551  /**
29552   * Returns whether or not the player is in the "ended" state.
29553   *
29554   * @return {Boolean} True if the player is in the ended state, false if not.
29555   * @method Player#ended
29556   */
29557  'ended',
29558  /**
29559   * Returns whether or not the player is in the "seeking" state.
29560   *
29561   * @return {Boolean} True if the player is in the seeking state, false if not.
29562   * @method Player#seeking
29563   */
29564  'seeking',
29565  /**
29566   * Returns the TimeRanges of the media that are currently available
29567   * for seeking to.
29568   *
29569   * @return {TimeRanges} the seekable intervals of the media timeline
29570   * @method Player#seekable
29571   */
29572  'seekable',
29573  /**
29574   * Returns the current state of network activity for the element, from
29575   * the codes in the list below.
29576   * - NETWORK_EMPTY (numeric value 0)
29577   *   The element has not yet been initialised. All attributes are in
29578   *   their initial states.
29579   * - NETWORK_IDLE (numeric value 1)
29580   *   The element's resource selection algorithm is active and has
29581   *   selected a resource, but it is not actually using the network at
29582   *   this time.
29583   * - NETWORK_LOADING (numeric value 2)
29584   *   The user agent is actively trying to download data.
29585   * - NETWORK_NO_SOURCE (numeric value 3)
29586   *   The element's resource selection algorithm is active, but it has
29587   *   not yet found a resource to use.
29588   *
29589   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
29590   * @return {number} the current network activity state
29591   * @method Player#networkState
29592   */
29593  'networkState',
29594  /**
29595   * Returns a value that expresses the current state of the element
29596   * with respect to rendering the current playback position, from the
29597   * codes in the list below.
29598   * - HAVE_NOTHING (numeric value 0)
29599   *   No information regarding the media resource is available.
29600   * - HAVE_METADATA (numeric value 1)
29601   *   Enough of the resource has been obtained that the duration of the
29602   *   resource is available.
29603   * - HAVE_CURRENT_DATA (numeric value 2)
29604   *   Data for the immediate current playback position is available.
29605   * - HAVE_FUTURE_DATA (numeric value 3)
29606   *   Data for the immediate current playback position is available, as
29607   *   well as enough data for the user agent to advance the current
29608   *   playback position in the direction of playback.
29609   * - HAVE_ENOUGH_DATA (numeric value 4)
29610   *   The user agent estimates that enough data is available for
29611   *   playback to proceed uninterrupted.
29612   *
29613   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
29614   * @return {number} the current playback rendering state
29615   * @method Player#readyState
29616   */
29617  'readyState'].forEach(function (fn) {
29618    Player.prototype[fn] = function () {
29619      return this.techGet_(fn);
29620    };
29621  });
29622  TECH_EVENTS_RETRIGGER.forEach(function (event) {
29623    Player.prototype["handleTech" + toTitleCase$1(event) + "_"] = function () {
29624      return this.trigger(event);
29625    };
29626  });
29627  /**
29628   * Fired when the player has initial duration and dimension information
29629   *
29630   * @event Player#loadedmetadata
29631   * @type {EventTarget~Event}
29632   */
29633
29634  /**
29635   * Fired when the player has downloaded data at the current playback position
29636   *
29637   * @event Player#loadeddata
29638   * @type {EventTarget~Event}
29639   */
29640
29641  /**
29642   * Fired when the current playback position has changed *
29643   * During playback this is fired every 15-250 milliseconds, depending on the
29644   * playback technology in use.
29645   *
29646   * @event Player#timeupdate
29647   * @type {EventTarget~Event}
29648   */
29649
29650  /**
29651   * Fired when the volume changes
29652   *
29653   * @event Player#volumechange
29654   * @type {EventTarget~Event}
29655   */
29656
29657  /**
29658   * Reports whether or not a player has a plugin available.
29659   *
29660   * This does not report whether or not the plugin has ever been initialized
29661   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
29662   *
29663   * @method Player#hasPlugin
29664   * @param  {string}  name
29665   *         The name of a plugin.
29666   *
29667   * @return {boolean}
29668   *         Whether or not this player has the requested plugin available.
29669   */
29670
29671  /**
29672   * Reports whether or not a player is using a plugin by name.
29673   *
29674   * For basic plugins, this only reports whether the plugin has _ever_ been
29675   * initialized on this player.
29676   *
29677   * @method Player#usingPlugin
29678   * @param  {string} name
29679   *         The name of a plugin.
29680   *
29681   * @return {boolean}
29682   *         Whether or not this player is using the requested plugin.
29683   */
29684
29685  Component$1.registerComponent('Player', Player);
29686
29687  var setPrototypeOf = createCommonjsModule(function (module) {
29688    function _setPrototypeOf(o, p) {
29689      module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
29690        o.__proto__ = p;
29691        return o;
29692      };
29693
29694      return _setPrototypeOf(o, p);
29695    }
29696
29697    module.exports = _setPrototypeOf;
29698  });
29699
29700  function _isNativeReflectConstruct() {
29701    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
29702    if (Reflect.construct.sham) return false;
29703    if (typeof Proxy === "function") return true;
29704
29705    try {
29706      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
29707      return true;
29708    } catch (e) {
29709      return false;
29710    }
29711  }
29712
29713  var isNativeReflectConstruct = _isNativeReflectConstruct;
29714
29715  var construct = createCommonjsModule(function (module) {
29716    function _construct(Parent, args, Class) {
29717      if (isNativeReflectConstruct()) {
29718        module.exports = _construct = Reflect.construct;
29719      } else {
29720        module.exports = _construct = function _construct(Parent, args, Class) {
29721          var a = [null];
29722          a.push.apply(a, args);
29723          var Constructor = Function.bind.apply(Parent, a);
29724          var instance = new Constructor();
29725          if (Class) setPrototypeOf(instance, Class.prototype);
29726          return instance;
29727        };
29728      }
29729
29730      return _construct.apply(null, arguments);
29731    }
29732
29733    module.exports = _construct;
29734  });
29735
29736  /**
29737   * The base plugin name.
29738   *
29739   * @private
29740   * @constant
29741   * @type {string}
29742   */
29743
29744  var BASE_PLUGIN_NAME = 'plugin';
29745  /**
29746   * The key on which a player's active plugins cache is stored.
29747   *
29748   * @private
29749   * @constant
29750   * @type     {string}
29751   */
29752
29753  var PLUGIN_CACHE_KEY = 'activePlugins_';
29754  /**
29755   * Stores registered plugins in a private space.
29756   *
29757   * @private
29758   * @type    {Object}
29759   */
29760
29761  var pluginStorage = {};
29762  /**
29763   * Reports whether or not a plugin has been registered.
29764   *
29765   * @private
29766   * @param   {string} name
29767   *          The name of a plugin.
29768   *
29769   * @return {boolean}
29770   *          Whether or not the plugin has been registered.
29771   */
29772
29773  var pluginExists = function pluginExists(name) {
29774    return pluginStorage.hasOwnProperty(name);
29775  };
29776  /**
29777   * Get a single registered plugin by name.
29778   *
29779   * @private
29780   * @param   {string} name
29781   *          The name of a plugin.
29782   *
29783   * @return {Function|undefined}
29784   *          The plugin (or undefined).
29785   */
29786
29787
29788  var getPlugin = function getPlugin(name) {
29789    return pluginExists(name) ? pluginStorage[name] : undefined;
29790  };
29791  /**
29792   * Marks a plugin as "active" on a player.
29793   *
29794   * Also, ensures that the player has an object for tracking active plugins.
29795   *
29796   * @private
29797   * @param   {Player} player
29798   *          A Video.js player instance.
29799   *
29800   * @param   {string} name
29801   *          The name of a plugin.
29802   */
29803
29804
29805  var markPluginAsActive = function markPluginAsActive(player, name) {
29806    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
29807    player[PLUGIN_CACHE_KEY][name] = true;
29808  };
29809  /**
29810   * Triggers a pair of plugin setup events.
29811   *
29812   * @private
29813   * @param  {Player} player
29814   *         A Video.js player instance.
29815   *
29816   * @param  {Plugin~PluginEventHash} hash
29817   *         A plugin event hash.
29818   *
29819   * @param  {boolean} [before]
29820   *         If true, prefixes the event name with "before". In other words,
29821   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
29822   */
29823
29824
29825  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
29826    var eventName = (before ? 'before' : '') + 'pluginsetup';
29827    player.trigger(eventName, hash);
29828    player.trigger(eventName + ':' + hash.name, hash);
29829  };
29830  /**
29831   * Takes a basic plugin function and returns a wrapper function which marks
29832   * on the player that the plugin has been activated.
29833   *
29834   * @private
29835   * @param   {string} name
29836   *          The name of the plugin.
29837   *
29838   * @param   {Function} plugin
29839   *          The basic plugin.
29840   *
29841   * @return {Function}
29842   *          A wrapper function for the given plugin.
29843   */
29844
29845
29846  var createBasicPlugin = function createBasicPlugin(name, plugin) {
29847    var basicPluginWrapper = function basicPluginWrapper() {
29848      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
29849      // regardless, but we want the hash to be consistent with the hash provided
29850      // for advanced plugins.
29851      //
29852      // The only potentially counter-intuitive thing here is the `instance` in
29853      // the "pluginsetup" event is the value returned by the `plugin` function.
29854      triggerSetupEvent(this, {
29855        name: name,
29856        plugin: plugin,
29857        instance: null
29858      }, true);
29859      var instance = plugin.apply(this, arguments);
29860      markPluginAsActive(this, name);
29861      triggerSetupEvent(this, {
29862        name: name,
29863        plugin: plugin,
29864        instance: instance
29865      });
29866      return instance;
29867    };
29868
29869    Object.keys(plugin).forEach(function (prop) {
29870      basicPluginWrapper[prop] = plugin[prop];
29871    });
29872    return basicPluginWrapper;
29873  };
29874  /**
29875   * Takes a plugin sub-class and returns a factory function for generating
29876   * instances of it.
29877   *
29878   * This factory function will replace itself with an instance of the requested
29879   * sub-class of Plugin.
29880   *
29881   * @private
29882   * @param   {string} name
29883   *          The name of the plugin.
29884   *
29885   * @param   {Plugin} PluginSubClass
29886   *          The advanced plugin.
29887   *
29888   * @return {Function}
29889   */
29890
29891
29892  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
29893    // Add a `name` property to the plugin prototype so that each plugin can
29894    // refer to itself by name.
29895    PluginSubClass.prototype.name = name;
29896    return function () {
29897      triggerSetupEvent(this, {
29898        name: name,
29899        plugin: PluginSubClass,
29900        instance: null
29901      }, true);
29902
29903      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
29904        args[_key] = arguments[_key];
29905      }
29906
29907      var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
29908
29909
29910      this[name] = function () {
29911        return instance;
29912      };
29913
29914      triggerSetupEvent(this, instance.getEventHash());
29915      return instance;
29916    };
29917  };
29918  /**
29919   * Parent class for all advanced plugins.
29920   *
29921   * @mixes   module:evented~EventedMixin
29922   * @mixes   module:stateful~StatefulMixin
29923   * @fires   Player#beforepluginsetup
29924   * @fires   Player#beforepluginsetup:$name
29925   * @fires   Player#pluginsetup
29926   * @fires   Player#pluginsetup:$name
29927   * @listens Player#dispose
29928   * @throws  {Error}
29929   *          If attempting to instantiate the base {@link Plugin} class
29930   *          directly instead of via a sub-class.
29931   */
29932
29933
29934  var Plugin = /*#__PURE__*/function () {
29935    /**
29936     * Creates an instance of this class.
29937     *
29938     * Sub-classes should call `super` to ensure plugins are properly initialized.
29939     *
29940     * @param {Player} player
29941     *        A Video.js player instance.
29942     */
29943    function Plugin(player) {
29944      if (this.constructor === Plugin) {
29945        throw new Error('Plugin must be sub-classed; not directly instantiated.');
29946      }
29947
29948      this.player = player;
29949
29950      if (!this.log) {
29951        this.log = this.player.log.createLogger(this.name);
29952      } // Make this object evented, but remove the added `trigger` method so we
29953      // use the prototype version instead.
29954
29955
29956      evented(this);
29957      delete this.trigger;
29958      stateful(this, this.constructor.defaultState);
29959      markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
29960      // it later easily.
29961
29962      this.dispose = this.dispose.bind(this); // If the player is disposed, dispose the plugin.
29963
29964      player.on('dispose', this.dispose);
29965    }
29966    /**
29967     * Get the version of the plugin that was set on <pluginName>.VERSION
29968     */
29969
29970
29971    var _proto = Plugin.prototype;
29972
29973    _proto.version = function version() {
29974      return this.constructor.VERSION;
29975    }
29976    /**
29977     * Each event triggered by plugins includes a hash of additional data with
29978     * conventional properties.
29979     *
29980     * This returns that object or mutates an existing hash.
29981     *
29982     * @param   {Object} [hash={}]
29983     *          An object to be used as event an event hash.
29984     *
29985     * @return {Plugin~PluginEventHash}
29986     *          An event hash object with provided properties mixed-in.
29987     */
29988    ;
29989
29990    _proto.getEventHash = function getEventHash(hash) {
29991      if (hash === void 0) {
29992        hash = {};
29993      }
29994
29995      hash.name = this.name;
29996      hash.plugin = this.constructor;
29997      hash.instance = this;
29998      return hash;
29999    }
30000    /**
30001     * Triggers an event on the plugin object and overrides
30002     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
30003     *
30004     * @param   {string|Object} event
30005     *          An event type or an object with a type property.
30006     *
30007     * @param   {Object} [hash={}]
30008     *          Additional data hash to merge with a
30009     *          {@link Plugin~PluginEventHash|PluginEventHash}.
30010     *
30011     * @return {boolean}
30012     *          Whether or not default was prevented.
30013     */
30014    ;
30015
30016    _proto.trigger = function trigger$1(event, hash) {
30017      if (hash === void 0) {
30018        hash = {};
30019      }
30020
30021      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
30022    }
30023    /**
30024     * Handles "statechanged" events on the plugin. No-op by default, override by
30025     * subclassing.
30026     *
30027     * @abstract
30028     * @param    {Event} e
30029     *           An event object provided by a "statechanged" event.
30030     *
30031     * @param    {Object} e.changes
30032     *           An object describing changes that occurred with the "statechanged"
30033     *           event.
30034     */
30035    ;
30036
30037    _proto.handleStateChanged = function handleStateChanged(e) {}
30038    /**
30039     * Disposes a plugin.
30040     *
30041     * Subclasses can override this if they want, but for the sake of safety,
30042     * it's probably best to subscribe the "dispose" event.
30043     *
30044     * @fires Plugin#dispose
30045     */
30046    ;
30047
30048    _proto.dispose = function dispose() {
30049      var name = this.name,
30050          player = this.player;
30051      /**
30052       * Signals that a advanced plugin is about to be disposed.
30053       *
30054       * @event Plugin#dispose
30055       * @type  {EventTarget~Event}
30056       */
30057
30058      this.trigger('dispose');
30059      this.off();
30060      player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
30061      // references between the player and the plugin instance and nulling out
30062      // the plugin's state and replacing methods with a function that throws.
30063
30064      player[PLUGIN_CACHE_KEY][name] = false;
30065      this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
30066      // function, so that the plugin is ready to be set up again.
30067
30068      player[name] = createPluginFactory(name, pluginStorage[name]);
30069    }
30070    /**
30071     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
30072     *
30073     * @param   {string|Function} plugin
30074     *          If a string, matches the name of a plugin. If a function, will be
30075     *          tested directly.
30076     *
30077     * @return {boolean}
30078     *          Whether or not a plugin is a basic plugin.
30079     */
30080    ;
30081
30082    Plugin.isBasic = function isBasic(plugin) {
30083      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
30084      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
30085    }
30086    /**
30087     * Register a Video.js plugin.
30088     *
30089     * @param   {string} name
30090     *          The name of the plugin to be registered. Must be a string and
30091     *          must not match an existing plugin or a method on the `Player`
30092     *          prototype.
30093     *
30094     * @param   {Function} plugin
30095     *          A sub-class of `Plugin` or a function for basic plugins.
30096     *
30097     * @return {Function}
30098     *          For advanced plugins, a factory function for that plugin. For
30099     *          basic plugins, a wrapper function that initializes the plugin.
30100     */
30101    ;
30102
30103    Plugin.registerPlugin = function registerPlugin(name, plugin) {
30104      if (typeof name !== 'string') {
30105        throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
30106      }
30107
30108      if (pluginExists(name)) {
30109        log$1.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
30110      } else if (Player.prototype.hasOwnProperty(name)) {
30111        throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
30112      }
30113
30114      if (typeof plugin !== 'function') {
30115        throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
30116      }
30117
30118      pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
30119      // the base Plugin class).
30120
30121      if (name !== BASE_PLUGIN_NAME) {
30122        if (Plugin.isBasic(plugin)) {
30123          Player.prototype[name] = createBasicPlugin(name, plugin);
30124        } else {
30125          Player.prototype[name] = createPluginFactory(name, plugin);
30126        }
30127      }
30128
30129      return plugin;
30130    }
30131    /**
30132     * De-register a Video.js plugin.
30133     *
30134     * @param  {string} name
30135     *         The name of the plugin to be de-registered. Must be a string that
30136     *         matches an existing plugin.
30137     *
30138     * @throws {Error}
30139     *         If an attempt is made to de-register the base plugin.
30140     */
30141    ;
30142
30143    Plugin.deregisterPlugin = function deregisterPlugin(name) {
30144      if (name === BASE_PLUGIN_NAME) {
30145        throw new Error('Cannot de-register base plugin.');
30146      }
30147
30148      if (pluginExists(name)) {
30149        delete pluginStorage[name];
30150        delete Player.prototype[name];
30151      }
30152    }
30153    /**
30154     * Gets an object containing multiple Video.js plugins.
30155     *
30156     * @param   {Array} [names]
30157     *          If provided, should be an array of plugin names. Defaults to _all_
30158     *          plugin names.
30159     *
30160     * @return {Object|undefined}
30161     *          An object containing plugin(s) associated with their name(s) or
30162     *          `undefined` if no matching plugins exist).
30163     */
30164    ;
30165
30166    Plugin.getPlugins = function getPlugins(names) {
30167      if (names === void 0) {
30168        names = Object.keys(pluginStorage);
30169      }
30170
30171      var result;
30172      names.forEach(function (name) {
30173        var plugin = getPlugin(name);
30174
30175        if (plugin) {
30176          result = result || {};
30177          result[name] = plugin;
30178        }
30179      });
30180      return result;
30181    }
30182    /**
30183     * Gets a plugin's version, if available
30184     *
30185     * @param   {string} name
30186     *          The name of a plugin.
30187     *
30188     * @return {string}
30189     *          The plugin's version or an empty string.
30190     */
30191    ;
30192
30193    Plugin.getPluginVersion = function getPluginVersion(name) {
30194      var plugin = getPlugin(name);
30195      return plugin && plugin.VERSION || '';
30196    };
30197
30198    return Plugin;
30199  }();
30200  /**
30201   * Gets a plugin by name if it exists.
30202   *
30203   * @static
30204   * @method   getPlugin
30205   * @memberOf Plugin
30206   * @param    {string} name
30207   *           The name of a plugin.
30208   *
30209   * @returns  {Function|undefined}
30210   *           The plugin (or `undefined`).
30211   */
30212
30213
30214  Plugin.getPlugin = getPlugin;
30215  /**
30216   * The name of the base plugin class as it is registered.
30217   *
30218   * @type {string}
30219   */
30220
30221  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
30222  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
30223  /**
30224   * Documented in player.js
30225   *
30226   * @ignore
30227   */
30228
30229  Player.prototype.usingPlugin = function (name) {
30230    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
30231  };
30232  /**
30233   * Documented in player.js
30234   *
30235   * @ignore
30236   */
30237
30238
30239  Player.prototype.hasPlugin = function (name) {
30240    return !!pluginExists(name);
30241  };
30242  /**
30243   * Signals that a plugin is about to be set up on a player.
30244   *
30245   * @event    Player#beforepluginsetup
30246   * @type     {Plugin~PluginEventHash}
30247   */
30248
30249  /**
30250   * Signals that a plugin is about to be set up on a player - by name. The name
30251   * is the name of the plugin.
30252   *
30253   * @event    Player#beforepluginsetup:$name
30254   * @type     {Plugin~PluginEventHash}
30255   */
30256
30257  /**
30258   * Signals that a plugin has just been set up on a player.
30259   *
30260   * @event    Player#pluginsetup
30261   * @type     {Plugin~PluginEventHash}
30262   */
30263
30264  /**
30265   * Signals that a plugin has just been set up on a player - by name. The name
30266   * is the name of the plugin.
30267   *
30268   * @event    Player#pluginsetup:$name
30269   * @type     {Plugin~PluginEventHash}
30270   */
30271
30272  /**
30273   * @typedef  {Object} Plugin~PluginEventHash
30274   *
30275   * @property {string} instance
30276   *           For basic plugins, the return value of the plugin function. For
30277   *           advanced plugins, the plugin instance on which the event is fired.
30278   *
30279   * @property {string} name
30280   *           The name of the plugin.
30281   *
30282   * @property {string} plugin
30283   *           For basic plugins, the plugin function. For advanced plugins, the
30284   *           plugin class/constructor.
30285   */
30286
30287  function _inherits(subClass, superClass) {
30288    if (typeof superClass !== "function" && superClass !== null) {
30289      throw new TypeError("Super expression must either be null or a function");
30290    }
30291
30292    subClass.prototype = Object.create(superClass && superClass.prototype, {
30293      constructor: {
30294        value: subClass,
30295        writable: true,
30296        configurable: true
30297      }
30298    });
30299    if (superClass) setPrototypeOf(subClass, superClass);
30300  }
30301
30302  var inherits = _inherits;
30303
30304  /**
30305   * @file extend.js
30306   * @module extend
30307   */
30308  /**
30309   * Used to subclass an existing class by emulating ES subclassing using the
30310   * `extends` keyword.
30311   *
30312   * @function
30313   * @example
30314   * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
30315   *   myCustomMethod: function() {
30316   *     // Do things in my method.
30317   *   }
30318   * });
30319   *
30320   * @param    {Function} superClass
30321   *           The class to inherit from
30322   *
30323   * @param    {Object}   [subClassMethods={}]
30324   *           Methods of the new class
30325   *
30326   * @return   {Function}
30327   *           The new class with subClassMethods that inherited superClass.
30328   */
30329
30330  var extend = function extend(superClass, subClassMethods) {
30331    if (subClassMethods === void 0) {
30332      subClassMethods = {};
30333    }
30334
30335    var subClass = function subClass() {
30336      superClass.apply(this, arguments);
30337    };
30338
30339    var methods = {};
30340
30341    if (typeof subClassMethods === 'object') {
30342      if (subClassMethods.constructor !== Object.prototype.constructor) {
30343        subClass = subClassMethods.constructor;
30344      }
30345
30346      methods = subClassMethods;
30347    } else if (typeof subClassMethods === 'function') {
30348      subClass = subClassMethods;
30349    }
30350
30351    inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility.
30352
30353
30354    if (superClass) {
30355      subClass.super_ = superClass;
30356    } // Extend subObj's prototype with functions and other properties from props
30357
30358
30359    for (var name in methods) {
30360      if (methods.hasOwnProperty(name)) {
30361        subClass.prototype[name] = methods[name];
30362      }
30363    }
30364
30365    return subClass;
30366  };
30367
30368  /**
30369   * @file video.js
30370   * @module videojs
30371   */
30372  /**
30373   * Normalize an `id` value by trimming off a leading `#`
30374   *
30375   * @private
30376   * @param   {string} id
30377   *          A string, maybe with a leading `#`.
30378   *
30379   * @return {string}
30380   *          The string, without any leading `#`.
30381   */
30382
30383  var normalizeId = function normalizeId(id) {
30384    return id.indexOf('#') === 0 ? id.slice(1) : id;
30385  };
30386  /**
30387   * The `videojs()` function doubles as the main function for users to create a
30388   * {@link Player} instance as well as the main library namespace.
30389   *
30390   * It can also be used as a getter for a pre-existing {@link Player} instance.
30391   * However, we _strongly_ recommend using `videojs.getPlayer()` for this
30392   * purpose because it avoids any potential for unintended initialization.
30393   *
30394   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
30395   * of our JSDoc template, we cannot properly document this as both a function
30396   * and a namespace, so its function signature is documented here.
30397   *
30398   * #### Arguments
30399   * ##### id
30400   * string|Element, **required**
30401   *
30402   * Video element or video element ID.
30403   *
30404   * ##### options
30405   * Object, optional
30406   *
30407   * Options object for providing settings.
30408   * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
30409   *
30410   * ##### ready
30411   * {@link Component~ReadyCallback}, optional
30412   *
30413   * A function to be called when the {@link Player} and {@link Tech} are ready.
30414   *
30415   * #### Return Value
30416   *
30417   * The `videojs()` function returns a {@link Player} instance.
30418   *
30419   * @namespace
30420   *
30421   * @borrows AudioTrack as AudioTrack
30422   * @borrows Component.getComponent as getComponent
30423   * @borrows module:computed-style~computedStyle as computedStyle
30424   * @borrows module:events.on as on
30425   * @borrows module:events.one as one
30426   * @borrows module:events.off as off
30427   * @borrows module:events.trigger as trigger
30428   * @borrows EventTarget as EventTarget
30429   * @borrows module:extend~extend as extend
30430   * @borrows module:fn.bind as bind
30431   * @borrows module:format-time.formatTime as formatTime
30432   * @borrows module:format-time.resetFormatTime as resetFormatTime
30433   * @borrows module:format-time.setFormatTime as setFormatTime
30434   * @borrows module:merge-options.mergeOptions as mergeOptions
30435   * @borrows module:middleware.use as use
30436   * @borrows Player.players as players
30437   * @borrows Plugin.registerPlugin as registerPlugin
30438   * @borrows Plugin.deregisterPlugin as deregisterPlugin
30439   * @borrows Plugin.getPlugins as getPlugins
30440   * @borrows Plugin.getPlugin as getPlugin
30441   * @borrows Plugin.getPluginVersion as getPluginVersion
30442   * @borrows Tech.getTech as getTech
30443   * @borrows Tech.registerTech as registerTech
30444   * @borrows TextTrack as TextTrack
30445   * @borrows module:time-ranges.createTimeRanges as createTimeRange
30446   * @borrows module:time-ranges.createTimeRanges as createTimeRanges
30447   * @borrows module:url.isCrossOrigin as isCrossOrigin
30448   * @borrows module:url.parseUrl as parseUrl
30449   * @borrows VideoTrack as VideoTrack
30450   *
30451   * @param  {string|Element}
30451 id
30452   *         Video element or video element ID.
30453   *
30454   * @param  {Object} [options]
30455   *         Options object for providing settings.
30456   *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
30457   *
30458   * @param  {Component~ReadyCallback} [ready]
30459   *         A function to be called when the {@link Player} and {@link Tech} are
30460   *         ready.
30461   *
30462   * @return {Player}
30463   *         The `videojs()` function returns a {@link Player|Player} instance.
30464   */
30465
30466
30467  function videojs(id, options, ready) {
30468    var player = videojs.getPlayer(id);
30469
30470    if (player) {
30471      if (options) {
30472        log$1.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
30473      }
30474
30475      if (ready) {
30476        player.ready(ready);
30477      }
30478
30479      return player;
30480    }
30481
30482    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
30483
30484    if (!isEl(el)) {
30485      throw new TypeError('The element or ID supplied is not valid. (videojs)');
30486    } // document.body.contains(el) will only check if el is contained within that one document.
30487    // This causes problems for elements in iframes.
30488    // Instead, use the element's ownerDocument instead of the global document.
30489    // This will make sure that the element is indeed in the dom of that document.
30490    // Additionally, check that the document in question has a default view.
30491    // If the document is no longer attached to the dom, the defaultView of the document will be null.
30492
30493
30494    if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
30495      log$1.warn('The element supplied is not included in the DOM');
30496    }
30497
30498    options = options || {};
30499    hooks('beforesetup').forEach(function (hookFunction) {
30500      var opts = hookFunction(el, mergeOptions$3(options));
30501
30502      if (!isObject$1(opts) || Array.isArray(opts)) {
30503        log$1.error('please return an object in beforesetup hooks');
30504        return;
30505      }
30506
30507      options = mergeOptions$3(options, opts);
30508    }); // We get the current "Player" component here in case an integration has
30509    // replaced it with a custom player.
30510
30511    var PlayerComponent = Component$1.getComponent('Player');
30512    player = new PlayerComponent(el, options, ready);
30513    hooks('setup').forEach(function (hookFunction) {
30514      return hookFunction(player);
30515    });
30516    return player;
30517  }
30518
30519  videojs.hooks_ = hooks_;
30520  videojs.hooks = hooks;
30521  videojs.hook = hook;
30522  videojs.hookOnce = hookOnce;
30523  videojs.removeHook = removeHook; // Add default styles
30524
30525  if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
30526    var style = $('.vjs-styles-defaults');
30527
30528    if (!style) {
30529      style = createStyleElement('vjs-styles-defaults');
30530      var head = $('head');
30531
30532      if (head) {
30533        head.insertBefore(style, head.firstChild);
30534      }
30535
30536      setTextContent(style, "\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ");
30537    }
30538  } // Run Auto-load players
30539  // You have to wait at least once in case this script is loaded after your
30540  // video in the DOM (weird behavior only with minified version)
30541
30542
30543  autoSetupTimeout(1, videojs);
30544  /**
30545   * Current Video.js version. Follows [semantic versioning](https://semver.org/).
30546   *
30547   * @type {string}
30548   */
30549
30550  videojs.VERSION = version$5;
30551  /**
30552   * The global options object. These are the settings that take effect
30553   * if no overrides are specified when the player is created.
30554   *
30555   * @type {Object}
30556   */
30557
30558  videojs.options = Player.prototype.options_;
30559  /**
30560   * Get an object with the currently created players, keyed by player ID
30561   *
30562   * @return {Object}
30563   *         The created players
30564   */
30565
30566  videojs.getPlayers = function () {
30567    return Player.players;
30568  };
30569  /**
30570   * Get a single player based on an ID or DOM element.
30571   *
30572   * This is useful if you want to check if an element or ID has an associated
30573   * Video.js player, but not create one if it doesn't.
30574   *
30575   * @param   {string|Element}
30575 id
30576   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
30577   *          or a string matching the `id` of such an element.
30578   *
30579   * @return {Player|undefined}
30580   *          A player instance or `undefined` if there is no player instance
30581   *          matching the argument.
30582   */
30583
30584
30585  videojs.getPlayer = function (id) {
30586    var players = Player.players;
30587    var tag;
30588
30589    if (typeof id === 'string') {
30590      var nId = normalizeId(id);
30591      var player = players[nId];
30592
30593      if (player) {
30594        return player;
30595      }
30596
30597      tag = $('#' + nId);
30598    } else {
30599      tag = id;
30600    }
30601
30602    if (isEl(tag)) {
30603      var _tag = tag,
30604          _player = _tag.player,
30605          playerId = _tag.playerId; // Element may have a `player` property referring to an already created
30606      // player instance. If so, return that.
30607
30608      if (_player || players[playerId]) {
30609        return _player || players[playerId];
30610      }
30611    }
30612  };
30613  /**
30614   * Returns an array of all current players.
30615   *
30616   * @return {Array}
30617   *         An array of all players. The array will be in the order that
30618   *         `Object.keys` provides, which could potentially vary between
30619   *         JavaScript engines.
30620   *
30621   */
30622
30623
30624  videojs.getAllPlayers = function () {
30625    return (// Disposed players leave a key with a `null` value, so we need to make sure
30626      // we filter those out.
30627      Object.keys(Player.players).map(function (k) {
30628        return Player.players[k];
30629      }).filter(Boolean)
30630    );
30631  };
30632
30633  videojs.players = Player.players;
30634  videojs.getComponent = Component$1.getComponent;
30635  /**
30636   * Register a component so it can referred to by name. Used when adding to other
30637   * components, either through addChild `component.addChild('myComponent')` or through
30638   * default children options  `{ children: ['myComponent'] }`.
30639   *
30640   * > NOTE: You could also just initialize the component before adding.
30641   * `component.addChild(new MyComponent());`
30642   *
30643   * @param {string} name
30644   *        The class name of the component
30645   *
30646   * @param {Component} comp
30647   *        The component class
30648   *
30649   * @return {Component}
30650   *         The newly registered component
30651   */
30652
30653  videojs.registerComponent = function (name, comp) {
30654    if (Tech.isTech(comp)) {
30655      log$1.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
30656    }
30657
30658    Component$1.registerComponent.call(Component$1, name, comp);
30659  };
30660
30661  videojs.getTech = Tech.getTech;
30662  videojs.registerTech = Tech.registerTech;
30663  videojs.use = use;
30664  /**
30665   * An object that can be returned by a middleware to signify
30666   * that the middleware is being terminated.
30667   *
30668   * @type {object}
30669   * @property {object} middleware.TERMINATOR
30670   */
30671
30672  Object.defineProperty(videojs, 'middleware', {
30673    value: {},
30674    writeable: false,
30675    enumerable: true
30676  });
30677  Object.defineProperty(videojs.middleware, 'TERMINATOR', {
30678    value: TERMINATOR,
30679    writeable: false,
30680    enumerable: true
30681  });
30682  /**
30683   * A reference to the {@link module:browser|browser utility module} as an object.
30684   *
30685   * @type {Object}
30686   * @see  {@link module:browser|browser}
30687   */
30688
30689  videojs.browser = browser;
30690  /**
30691   * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
30692   * included for backward-compatibility with 4.x.
30693   *
30694   * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
30695   * @type {boolean}
30696   */
30697
30698  videojs.TOUCH_ENABLED = TOUCH_ENABLED;
30699  videojs.extend = extend;
30700  videojs.mergeOptions = mergeOptions$3;
30701  videojs.bind = bind;
30702  videojs.registerPlugin = Plugin.registerPlugin;
30703  videojs.deregisterPlugin = Plugin.deregisterPlugin;
30704  /**
30705   * Deprecated method to register a plugin with Video.js
30706   *
30707   * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
30708   *
30709   * @param {string} name
30710   *        The plugin name
30711   *
30712   * @param {Plugin|Function} plugin
30713   *         The plugin sub-class or function
30714   */
30715
30716  videojs.plugin = function (name, plugin) {
30717    log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
30718    return Plugin.registerPlugin(name, plugin);
30719  };
30720
30721  videojs.getPlugins = Plugin.getPlugins;
30722  videojs.getPlugin = Plugin.getPlugin;
30723  videojs.getPluginVersion = Plugin.getPluginVersion;
30724  /**
30725   * Adding languages so that they're available to all players.
30726   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
30727   *
30728   * @param {string} code
30729   *        The language code or dictionary property
30730   *
30731   * @param {Object} data
30732   *        The data values to be translated
30733   *
30734   * @return {Object}
30735   *         The resulting language dictionary object
30736   */
30737
30738  videojs.addLanguage = function (code, data) {
30739    var _mergeOptions;
30740
30741    code = ('' + code).toLowerCase();
30742    videojs.options.languages = mergeOptions$3(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
30743    return videojs.options.languages[code];
30744  };
30745  /**
30746   * A reference to the {@link module:log|log utility module} as an object.
30747   *
30748   * @type {Function}
30749   * @see  {@link module:log|log}
30750   */
30751
30752
30753  videojs.log = log$1;
30754  videojs.createLogger = createLogger;
30755  videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges;
vendor: 6,525 bytes, lines 30756-30916
30756  videojs.formatTime = formatTime;
30757  videojs.setFormatTime = setFormatTime;
30758  videojs.resetFormatTime = resetFormatTime;
30759  videojs.parseUrl = parseUrl;
30760  videojs.isCrossOrigin = isCrossOrigin;
30761  videojs.EventTarget = EventTarget$2;
30762  videojs.on = on;
30763  videojs.one = one;
30764  videojs.off = off;
30765  videojs.trigger = trigger;
30766  /**
30767   * A cross-browser XMLHttpRequest wrapper.
30768   *
30769   * @function
30770   * @param    {Object} options
30771   *           Settings for the request.
30772   *
30773   * @return   {XMLHttpRequest|XDomainRequest}
30774   *           The request object.
30775   *
30776   * @see      https://github.com/Raynos/xhr
30777   */
30778
30779  videojs.xhr = lib;
30780  videojs.TextTrack = TextTrack;
30781  videojs.AudioTrack = AudioTrack;
30782  videojs.VideoTrack = VideoTrack;
30783  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
30784    videojs[k] = function () {
30785      log$1.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
30786      return Dom[k].apply(null, arguments);
30787    };
30788  });
30789  videojs.computedStyle = computedStyle;
30790  /**
30791   * A reference to the {@link module:dom|DOM utility module} as an object.
30792   *
30793   * @type {Object}
30794   * @see  {@link module:dom|dom}
30795   */
30796
30797  videojs.dom = Dom;
30798  /**
30799   * A reference to the {@link module:url|URL utility module} as an object.
30800   *
30801   * @type {Object}
30802   * @see  {@link module:url|url}
30803   */
30804
30805  videojs.url = Url;
30806  videojs.defineLazyProperty = defineLazyProperty; // Adding less ambiguous text for fullscreen button.
30807  // In a major update this could become the default text and key.
30808
30809  videojs.addLanguage('en', {
30810    'Non-Fullscreen': 'Exit Fullscreen'
30811  });
30812
30813  var urlToolkit = createCommonjsModule(function (module, exports) {
30814    // see https://tools.ietf.org/html/rfc1808
30815    (function (root) {
30816      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/?#]*\/)*[^;?#]*)?(;[^?#]*)?(\?[^#]*)?(#[^]*)?$/;
30817      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)([^]*)$/;
30818      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
30819      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
30820      var URLToolkit = {
30821        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
30822        // E.g
30823        // With opts.alwaysNormalize = false (default, spec compliant)
30824        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
30825        // With opts.alwaysNormalize = true (not spec compliant)
30826        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
30827        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
30828          opts = opts || {}; // remove any remaining space and CRLF
30829
30830          baseURL = baseURL.trim();
30831          relativeURL = relativeURL.trim();
30832
30833          if (!relativeURL) {
30834            // 2a) If the embedded URL is entirely empty, it inherits the
30835            // entire base URL (i.e., is set equal to the base URL)
30836            // and we are done.
30837            if (!opts.alwaysNormalize) {
30838              return baseURL;
30839            }
30840
30841            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
30842
30843            if (!basePartsForNormalise) {
30844              throw new Error('Error trying to parse base URL.');
30845            }
30846
30847            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
30848            return URLToolkit.buildURLFromParts(basePartsForNormalise);
30849          }
30850
30851          var relativeParts = URLToolkit.parseURL(relativeURL);
30852
30853          if (!relativeParts) {
30854            throw new Error('Error trying to parse relative URL.');
30855          }
30856
30857          if (relativeParts.scheme) {
30858            // 2b) If the embedded URL starts with a scheme name, it is
30859            // interpreted as an absolute URL and we are done.
30860            if (!opts.alwaysNormalize) {
30861              return relativeURL;
30862            }
30863
30864            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
30865            return URLToolkit.buildURLFromParts(relativeParts);
30866          }
30867
30868          var baseParts = URLToolkit.parseURL(baseURL);
30869
30870          if (!baseParts) {
30871            throw new Error('Error trying to parse base URL.');
30872          }
30873
30874          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
30875            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
30876            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
30877            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
30878            baseParts.netLoc = pathParts[1];
30879            baseParts.path = pathParts[2];
30880          }
30881
30882          if (baseParts.netLoc && !baseParts.path) {
30883            baseParts.path = '/';
30884          }
30885
30886          var builtParts = {
30887            // 2c) Otherwise, the embedded URL inherits the scheme of
30888            // the base URL.
30889            scheme: baseParts.scheme,
30890            netLoc: relativeParts.netLoc,
30891            path: null,
30892            params: relativeParts.params,
30893            query: relativeParts.query,
30894            fragment: relativeParts.fragment
30895          };
30896
30897          if (!relativeParts.netLoc) {
30898            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
30899            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
30900            // (if any) of the base URL.
30901            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
30902            // path is not relative and we skip to Step 7.
30903
30904            if (relativeParts.path[0] !== '/') {
30905              if (!relativeParts.path) {
30906                // 5) If the embedded URL path is empty (and not preceded by a
30907                // slash), then the embedded URL inherits the base URL path
30908                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
30909                // step 7; otherwise, it inherits the <params> of the base
30910                // URL (if any) and
30911
30912                if (!relativeParts.params) {
30913                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
30914                  // step 7; otherwise, it inherits the <query> of the base
30915                  // URL (if any) and we skip to step 7.
30916
30917                  if (!relativeParts.query) {
30918                    builtParts.query = baseParts.query;
30919                  }
30920                }
30921              } else {
30922                // 6) The last segment of the base URL's path (anything
30923                // following the rightmost slash "/", or the entire path if no
30924                // slash is present) is removed and the embedded URL's path is
30925                // appended in its place.
30926                var baseURLPath = baseParts.path;
30927                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
30928                builtParts.path = URLToolkit.normalizePath(newPath);
30929              }
30930            }
30931          }
30932
30933          if (builtParts.path === null) {
30934            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
30935          }
30936
30937          return URLToolkit.buildURLFromParts(builtParts);
30938        },
30939        parseURL: function parseURL(url) {
30940          var parts = URL_REGEX.exec(url);
30941
30942          if (!parts) {
30943            return null;
30944          }
30945
30946          return {
30947            scheme: parts[1] || '',
30948            netLoc: parts[2] || '',
30949            path: parts[3] || '',
30950            params: parts[4] || '',
30951            query: parts[5] || '',
30952            fragment: parts[6] || ''
30953          };
30954        },
30955        normalizePath: function normalizePath(path) {
30956          // The following operations are
30957          // then applied, in order, to the new path:
30958          // 6a) All occurrences of "./", where "." is a complete path
30959          // segment, are removed.
30960          // 6b) If the path ends with "." as a complete path segment,
30961          // that "." is removed.
30962          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
30963          // complete path segment not equal to "..", are removed.
30964          // Removal of these path segments is performed iteratively,
30965          // removing the leftmost matching pattern on each iteration,
30966          // until no matching pattern remains.
30967          // 6d) If the path ends with "<segment>/..", where <segment> is a
30968          // complete path segment not equal to "..", that
30969          // "<segment>/.." is removed.
30970
30971          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {}
30972
30973          return path.split('').reverse().join('');
30974        },
30975        buildURLFromParts: function buildURLFromParts(parts) {
30976          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
30977        }
30978      };
30979      module.exports = URLToolkit;
30980    })();
30981  });
30982
30983  var DEFAULT_LOCATION = 'http://example.com';
30984
30985  var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
30986    // return early if we don't need to resolve
30987    if (/^[a-z]+:/i.test(relativeUrl)) {
30988      return relativeUrl;
30989    } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
30990
30991
30992    if (/^data:/.test(baseUrl)) {
30993      baseUrl = window_1.location && window_1.location.href || '';
30994    } // IE11 supports URL but not the URL constructor
30995    // feature detect the behavior we want
30996
30997
30998    var nativeURL = typeof window_1.URL === 'function';
30999    var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
31000    // and if baseUrl isn't an absolute url
31001
31002    var removeLocation = !window_1.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
31003
31004    if (nativeURL) {
31005      baseUrl = new window_1.URL(baseUrl, window_1.location || DEFAULT_LOCATION);
31006    } else if (!/\/\//i.test(baseUrl)) {
31007      baseUrl = urlToolkit.buildAbsoluteURL(window_1.location && window_1.location.href || '', baseUrl);
31008    }
31009
31010    if (nativeURL) {
31011      var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
31012      // and if we're location-less, remove the location
31013      // otherwise, return the url unmodified
31014
31015      if (removeLocation) {
31016        return newUrl.href.slice(DEFAULT_LOCATION.length);
31017      } else if (protocolLess) {
31018        return newUrl.href.slice(newUrl.protocol.length);
31019      }
31020
31021      return newUrl.href;
31022    }
31023
31024    return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
31025  };
31026
31027  /**
31028   * @file stream.js
31029   */
31030
31031  /**
31032   * A lightweight readable stream implemention that handles event dispatching.
31033   *
31034   * @class Stream
31035   */
31036  var Stream = /*#__PURE__*/function () {
31037    function Stream() {
31038      this.listeners = {};
31039    }
31040    /**
31041     * Add a listener for a specified event type.
31042     *
31043     * @param {string} type the event name
31044     * @param {Function} listener the callback to be invoked when an event of
31045     * the specified type occurs
31046     */
31047
31048
31049    var _proto = Stream.prototype;
31050
31051    _proto.on = function on(type, listener) {
31052      if (!this.listeners[type]) {
31053        this.listeners[type] = [];
31054      }
31055
31056      this.listeners[type].push(listener);
31057    }
31058    /**
31059     * Remove a listener for a specified event type.
31060     *
31061     * @param {string} type the event name
31062     * @param {Function} listener  a function previously registered for this
31063     * type of event through `on`
31064     * @return {boolean} if we could turn it off or not
31065     */
31066    ;
31067
31068    _proto.off = function off(type, listener) {
31069      if (!this.listeners[type]) {
31070        return false;
31071      }
31072
31073      var index = this.listeners[type].indexOf(listener); // TODO: which is better?
31074      // In Video.js we slice listener functions
31075      // on trigger so that it does not mess up the order
31076      // while we loop through.
31077      //
31078      // Here we slice on off so that the loop in trigger
31079      // can continue using it's old reference to loop without
31080      // messing up the order.
31081
31082      this.listeners[type] = this.listeners[type].slice(0);
31083      this.listeners[type].splice(index, 1);
31084      return index > -1;
31085    }
31086    /**
31087     * Trigger an event of the specified type on this stream. Any additional
31088     * arguments to this function are passed as parameters to event listeners.
31089     *
31090     * @param {string} type the event name
31091     */
31092    ;
31093
31094    _proto.trigger = function trigger(type) {
31095      var callbacks = this.listeners[type];
31096
31097      if (!callbacks) {
31098        return;
31099      } // Slicing the arguments on every invocation of this method
31100      // can add a significant amount of overhead. Avoid the
31101      // intermediate object creation for the common case of a
31102      // single callback argument
31103
31104
31105      if (arguments.length === 2) {
31106        var length = callbacks.length;
31107
31108        for (var i = 0; i < length; ++i) {
31109          callbacks[i].call(this, arguments[1]);
31110        }
31111      } else {
31112        var args = Array.prototype.slice.call(arguments, 1);
31113        var _length = callbacks.length;
31114
31115        for (var _i = 0; _i < _length; ++_i) {
31116          callbacks[_i].apply(this, args);
31117        }
31118      }
31119    }
31120    /**
31121     * Destroys the stream and cleans up.
31122     */
31123    ;
31124
31125    _proto.dispose = function dispose() {
31126      this.listeners = {};
31127    }
31128    /**
31129     * Forwards all `data` events on this stream to the destination stream. The
31130     * destination stream should provide a method `push` to receive the data
31131     * events as they arrive.
31132     *
31133     * @param {Stream} destination the stream that will receive all `data` events
31134     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
31135     */
31136    ;
31137
31138    _proto.pipe = function pipe(destination) {
31139      this.on('data', function (data) {
31140        destination.push(data);
31141      });
31142    };
31143
31144    return Stream;
31145  }();
31146
31147  var atob = function atob(s) {
31148    return window_1.atob ? window_1.atob(s) : Buffer.from(s, 'base64').toString('binary');
31149  };
31150
31151  function decodeB64ToUint8Array(b64Text) {
31152    var decodedString = atob(b64Text);
31153    var array = new Uint8Array(decodedString.length);
31154
31155    for (var i = 0; i < decodedString.length; i++) {
31156      array[i] = decodedString.charCodeAt(i);
31157    }
31158
31159    return array;
31160  }
31161
31162  /*! @name m3u8-parser @version 4.7.0 @license Apache-2.0 */
31163  /**
31164   * A stream that buffers string input and generates a `data` event for each
31165   * line.
31166   *
31167   * @class LineStream
31168   * @extends Stream
31169   */
31170
31171  var LineStream = /*#__PURE__*/function (_Stream) {
31172    inheritsLoose(LineStream, _Stream);
31173
31174    function LineStream() {
31175      var _this;
31176
31177      _this = _Stream.call(this) || this;
31178      _this.buffer = '';
31179      return _this;
31180    }
31181    /**
31182     * Add new data to be parsed.
31183     *
31184     * @param {string} data the text to process
31185     */
31186
31187
31188    var _proto = LineStream.prototype;
31189
31190    _proto.push = function push(data) {
31191      var nextNewline;
31192      this.buffer += data;
31193      nextNewline = this.buffer.indexOf('\n');
31194
31195      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
31196        this.trigger('data', this.buffer.substring(0, nextNewline));
31197        this.buffer = this.buffer.substring(nextNewline + 1);
31198      }
31199    };
31200
31201    return LineStream;
31202  }(Stream);
31203
31204  var TAB = String.fromCharCode(0x09);
31205
31206  var parseByterange = function parseByterange(byterangeString) {
31207    // optionally match and capture 0+ digits before `@`
31208    // optionally match and capture 0+ digits after `@`
31209    var match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
31210    var result = {};
31211
31212    if (match[1]) {
31213      result.length = parseInt(match[1], 10);
31214    }
31215
31216    if (match[2]) {
31217      result.offset = parseInt(match[2], 10);
31218    }
31219
31220    return result;
31221  };
31222  /**
31223   * "forgiving" attribute list psuedo-grammar:
31224   * attributes -> keyvalue (',' keyvalue)*
31225   * keyvalue   -> key '=' value
31226   * key        -> [^=]*
31227   * value      -> '"' [^"]* '"' | [^,]*
31228   */
31229
31230
31231  var attributeSeparator = function attributeSeparator() {
31232    var key = '[^=]*';
31233    var value = '"[^"]*"|[^,]*';
31234    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
31235    return new RegExp('(?:^|,)(' + keyvalue + ')');
31236  };
31237  /**
31238   * Parse attributes from a line given the separator
31239   *
31240   * @param {string} attributes the attribute line to parse
31241   */
31242
31243
31244  var parseAttributes$1 = function parseAttributes(attributes) {
31245    // split the string using attributes as the separator
31246    var attrs = attributes.split(attributeSeparator());
31247    var result = {};
31248    var i = attrs.length;
31249    var attr;
31250
31251    while (i--) {
31252      // filter out unmatched portions of the string
31253      if (attrs[i] === '') {
31254        continue;
31255      } // split the key and value
31256
31257
31258      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
31259
31260      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
31261      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
31262      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
31263      result[attr[0]] = attr[1];
31264    }
31265
31266    return result;
31267  };
31268  /**
31269   * A line-level M3U8 parser event stream. It expects to receive input one
31270   * line at a time and performs a context-free parse of its contents. A stream
31271   * interpretation of a manifest can be useful if the manifest is expected to
31272   * be too large to fit comfortably into memory or the entirety of the input
31273   * is not immediately available. Otherwise, it's probably much easier to work
31274   * with a regular `Parser` object.
31275   *
31276   * Produces `data` events with an object that captures the parser's
31277   * interpretation of the input. That object has a property `tag` that is one
31278   * of `uri`, `comment`, or `tag`. URIs only have a single additional
31279   * property, `line`, which captures the entirety of the input without
31280   * interpretation. Comments similarly have a single additional property
31281   * `text` which is the input without the leading `#`.
31282   *
31283   * Tags always have a property `tagType` which is the lower-cased version of
31284   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
31285   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
31286   * tags are given the tag type `unknown` and a single additional property
31287   * `data` with the remainder of the input.
31288   *
31289   * @class ParseStream
31290   * @extends Stream
31291   */
31292
31293
31294  var ParseStream = /*#__PURE__*/function (_Stream) {
31295    inheritsLoose(ParseStream, _Stream);
31296
31297    function ParseStream() {
31298      var _this;
31299
31300      _this = _Stream.call(this) || this;
31301      _this.customParsers = [];
31302      _this.tagMappers = [];
31303      return _this;
31304    }
31305    /**
31306     * Parses an additional line of input.
31307     *
31308     * @param {string} line a single line of an M3U8 file to parse
31309     */
31310
31311
31312    var _proto = ParseStream.prototype;
31313
31314    _proto.push = function push(line) {
31315      var _this2 = this;
31316
31317      var match;
31318      var event; // strip whitespace
31319
31320      line = line.trim();
31321
31322      if (line.length === 0) {
31323        // ignore empty lines
31324        return;
31325      } // URIs
31326
31327
31328      if (line[0] !== '#') {
31329        this.trigger('data', {
31330          type: 'uri',
31331          uri: line
31332        });
31333        return;
31334      } // map tags
31335
31336
31337      var newLines = this.tagMappers.reduce(function (acc, mapper) {
31338        var mappedLine = mapper(line);
31338 // skip if unchanged
31339
31340        if (mappedLine === line) {
31341          return acc;
31342        }
31343
31344        return acc.concat([mappedLine]);
31345      }, [line]);
31346      newLines.forEach(function (newLine) {
31347        for (var i = 0; i < _this2.customParsers.length; i++) {
31348          if (_this2.customParsers[i].call(_this2, newLine)) {
31349            return;
31350          }
31351        } // Comments
31352
31353
31354        if (newLine.indexOf('#EXT') !== 0) {
31355          _this2.trigger('data', {
31356            type: 'comment',
31357            text: newLine.slice(1)
31358          });
31359
31360          return;
31361        } // strip off any carriage returns here so the regex matching
31362        // doesn't have to account for them.
31363
31364
31365        newLine = newLine.replace('\r', ''); // Tags
31366
31367        match = /^#EXTM3U/.exec(newLine);
31368
31369        if (match) {
31370          _this2.trigger('data', {
31371            type: 'tag',
31372            tagType: 'm3u'
31373          });
31374
31375          return;
31376        }
31377
31378        match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
31379
31380        if (match) {
31381          event = {
31382            type: 'tag',
31383            tagType: 'inf'
31384          };
31385
31386          if (match[1]) {
31387            event.duration = parseFloat(match[1]);
31388          }
31389
31390          if (match[2]) {
31391            event.title = match[2];
31392          }
31393
31394          _this2.trigger('data', event);
31395
31396          return;
31397        }
31398
31399        match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
31400
31401        if (match) {
31402          event = {
31403            type: 'tag',
31404            tagType: 'targetduration'
31405          };
31406
31407          if (match[1]) {
31408            event.duration = parseInt(match[1], 10);
31409          }
31410
31411          _this2.trigger('data', event);
31412
31413          return;
31414        }
31415
31416        match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
31417
31418        if (match) {
31419          event = {
31420            type: 'tag',
31421            tagType: 'version'
31422          };
31423
31424          if (match[1]) {
31425            event.version = parseInt(match[1], 10);
31426          }
31427
31428          _this2.trigger('data', event);
31429
31430          return;
31431        }
31432
31433        match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
31434
31435        if (match) {
31436          event = {
31437            type: 'tag',
31438            tagType: 'media-sequence'
31439          };
31440
31441          if (match[1]) {
31442            event.number = parseInt(match[1], 10);
31443          }
31444
31445          _this2.trigger('data', event);
31446
31447          return;
31448        }
31449
31450        match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
31451
31452        if (match) {
31453          event = {
31454            type: 'tag',
31455            tagType: 'discontinuity-sequence'
31456          };
31457
31458          if (match[1]) {
31459            event.number = parseInt(match[1], 10);
31460          }
31461
31462          _this2.trigger('data', event);
31463
31464          return;
31465        }
31466
31467        match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
31468
31469        if (match) {
31470          event = {
31471            type: 'tag',
31472            tagType: 'playlist-type'
31473          };
31474
31475          if (match[1]) {
31476            event.playlistType = match[1];
31477          }
31478
31479          _this2.trigger('data', event);
31480
31481          return;
31482        }
31483
31484        match = /^#EXT-X-BYTERANGE:?(.*)?$/.exec(newLine);
31485
31486        if (match) {
31487          event = _extends_1(parseByterange(match[1]), {
31488            type: 'tag',
31489            tagType: 'byterange'
31490          });
31491
31492          _this2.trigger('data', event);
31493
31494          return;
31495        }
31496
31497        match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
31498
31499        if (match) {
31500          event = {
31501            type: 'tag',
31502            tagType: 'allow-cache'
31503          };
31504
31505          if (match[1]) {
31506            event.allowed = !/NO/.test(match[1]);
31507          }
31508
31509          _this2.trigger('data', event);
31510
31511          return;
31512        }
31513
31514        match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
31515
31516        if (match) {
31517          event = {
31518            type: 'tag',
31519            tagType: 'map'
31520          };
31521
31522          if (match[1]) {
31523            var attributes = parseAttributes$1(match[1]);
31524
31525            if (attributes.URI) {
31526              event.uri = attributes.URI;
31527            }
31528
31529            if (attributes.BYTERANGE) {
31530              event.byterange = parseByterange(attributes.BYTERANGE);
31531            }
31532          }
31533
31534          _this2.trigger('data', event);
31535
31536          return;
31537        }
31538
31539        match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
31540
31541        if (match) {
31542          event = {
31543            type: 'tag',
31544            tagType: 'stream-inf'
31545          };
31546
31547          if (match[1]) {
31548            event.attributes = parseAttributes$1(match[1]);
31549
31550            if (event.attributes.RESOLUTION) {
31551              var split = event.attributes.RESOLUTION.split('x');
31552              var resolution = {};
31553
31554              if (split[0]) {
31555                resolution.width = parseInt(split[0], 10);
31556              }
31557
31558              if (split[1]) {
31559                resolution.height = parseInt(split[1], 10);
31560              }
31561
31562              event.attributes.RESOLUTION = resolution;
31563            }
31564
31565            if (event.attributes.BANDWIDTH) {
31566              event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
31567            }
31568
31569            if (event.attributes['PROGRAM-ID']) {
31570              event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
31571            }
31572          }
31573
31574          _this2.trigger('data', event);
31575
31576          return;
31577        }
31578
31579        match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
31580
31581        if (match) {
31582          event = {
31583            type: 'tag',
31584            tagType: 'media'
31585          };
31586
31587          if (match[1]) {
31588            event.attributes = parseAttributes$1(match[1]);
31589          }
31590
31591          _this2.trigger('data', event);
31592
31593          return;
31594        }
31595
31596        match = /^#EXT-X-ENDLIST/.exec(newLine);
31597
31598        if (match) {
31599          _this2.trigger('data', {
31600            type: 'tag',
31601            tagType: 'endlist'
31602          });
31603
31604          return;
31605        }
31606
31607        match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
31608
31609        if (match) {
31610          _this2.trigger('data', {
31611            type: 'tag',
31612            tagType: 'discontinuity'
31613          });
31614
31615          return;
31616        }
31617
31618        match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
31619
31620        if (match) {
31621          event = {
31622            type: 'tag',
31623            tagType: 'program-date-time'
31624          };
31625
31626          if (match[1]) {
31627            event.dateTimeString = match[1];
31628            event.dateTimeObject = new Date(match[1]);
31629          }
31630
31631          _this2.trigger('data', event);
31632
31633          return;
31634        }
31635
31636        match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
31637
31638        if (match) {
31639          event = {
31640            type: 'tag',
31641            tagType: 'key'
31642          };
31643
31644          if (match[1]) {
31645            event.attributes = parseAttributes$1(match[1]); // parse the IV string into a Uint32Array
31646
31647            if (event.attributes.IV) {
31648              if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
31649                event.attributes.IV = event.attributes.IV.substring(2);
31650              }
31651
31652              event.attributes.IV = event.attributes.IV.match(/.{8}/g);
31653              event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
31654              event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
31655              event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
31656              event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
31657              event.attributes.IV = new Uint32Array(event.attributes.IV);
31658            }
31659          }
31660
31661          _this2.trigger('data', event);
31662
31663          return;
31664        }
31665
31666        match = /^#EXT-X-START:?(.*)$/.exec(newLine);
31667
31668        if (match) {
31669          event = {
31670            type: 'tag',
31671            tagType: 'start'
31672          };
31673
31674          if (match[1]) {
31675            event.attributes = parseAttributes$1(match[1]);
31676            event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
31677            event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
31678          }
31679
31680          _this2.trigger('data', event);
31681
31682          return;
31683        }
31684
31685        match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
31686
31687        if (match) {
31688          event = {
31689            type: 'tag',
31690            tagType: 'cue-out-cont'
31691          };
31692
31693          if (match[1]) {
31694            event.data = match[1];
31695          } else {
31696            event.data = '';
31697          }
31698
31699          _this2.trigger('data', event);
31700
31701          return;
31702        }
31703
31704        match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
31705
31706        if (match) {
31707          event = {
31708            type: 'tag',
31709            tagType: 'cue-out'
31710          };
31711
31712          if (match[1]) {
31713            event.data = match[1];
31714          } else {
31715            event.data = '';
31716          }
31717
31718          _this2.trigger('data', event);
31719
31720          return;
31721        }
31722
31723        match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
31724
31725        if (match) {
31726          event = {
31727            type: 'tag',
31728            tagType: 'cue-in'
31729          };
31730
31731          if (match[1]) {
31732            event.data = match[1];
31733          } else {
31734            event.data = '';
31735          }
31736
31737          _this2.trigger('data', event);
31738
31739          return;
31740        }
31741
31742        match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
31743
31744        if (match && match[1]) {
31745          event = {
31746            type: 'tag',
31747            tagType: 'skip'
31748          };
31749          event.attributes = parseAttributes$1(match[1]);
31750
31751          if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
31752            event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
31753          }
31754
31755          if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
31756            event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
31757          }
31758
31759          _this2.trigger('data', event);
31760
31761          return;
31762        }
31763
31764        match = /^#EXT-X-PART:(.*)$/.exec(newLine);
31765
31766        if (match && match[1]) {
31767          event = {
31768            type: 'tag',
31769            tagType: 'part'
31770          };
31771          event.attributes = parseAttributes$1(match[1]);
31772          ['DURATION'].forEach(function (key) {
31773            if (event.attributes.hasOwnProperty(key)) {
31774              event.attributes[key] = parseFloat(event.attributes[key]);
31775            }
31776          });
31777          ['INDEPENDENT', 'GAP'].forEach(function (key) {
31778            if (event.attributes.hasOwnProperty(key)) {
31779              event.attributes[key] = /YES/.test(event.attributes[key]);
31780            }
31781          });
31782
31783          if (event.attributes.hasOwnProperty('BYTERANGE')) {
31784            event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
31785          }
31786
31787          _this2.trigger('data', event);
31788
31789          return;
31790        }
31791
31792        match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
31793
31794        if (match && match[1]) {
31795          event = {
31796            type: 'tag',
31797            tagType: 'server-control'
31798          };
31799          event.attributes = parseAttributes$1(match[1]);
31800          ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
31801            if (event.attributes.hasOwnProperty(key)) {
31802              event.attributes[key] = parseFloat(event.attributes[key]);
31803            }
31804          });
31805          ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
31806            if (event.attributes.hasOwnProperty(key)) {
31807              event.attributes[key] = /YES/.test(event.attributes[key]);
31808            }
31809          });
31810
31811          _this2.trigger('data', event);
31812
31813          return;
31814        }
31815
31816        match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
31817
31818        if (match && match[1]) {
31819          event = {
31820            type: 'tag',
31821            tagType: 'part-inf'
31822          };
31823          event.attributes = parseAttributes$1(match[1]);
31824          ['PART-TARGET'].forEach(function (key) {
31825            if (event.attributes.hasOwnProperty(key)) {
31826              event.attributes[key] = parseFloat(event.attributes[key]);
31827            }
31828          });
31829
31830          _this2.trigger('data', event);
31831
31832          return;
31833        }
31834
31835        match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
31836
31837        if (match && match[1]) {
31838          event = {
31839            type: 'tag',
31840            tagType: 'preload-hint'
31841          };
31842          event.attributes = parseAttributes$1(match[1]);
31843          ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
31844            if (event.attributes.hasOwnProperty(key)) {
31845              event.attributes[key] = parseInt(event.attributes[key], 10);
31846              var subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
31847              event.attributes.byterange = event.attributes.byterange || {};
31848              event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
31849
31850              delete event.attributes[key];
31851            }
31852          });
31853
31854          _this2.trigger('data', event);
31855
31856          return;
31857        }
31858
31859        match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
31860
31861        if (match && match[1]) {
31862          event = {
31863            type: 'tag',
31864            tagType: 'rendition-report'
31865          };
31866          event.attributes = parseAttributes$1(match[1]);
31867          ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
31868            if (event.attributes.hasOwnProperty(key)) {
31869              event.attributes[key] = parseInt(event.attributes[key], 10);
31870            }
31871          });
31872
31873          _this2.trigger('data', event);
31874
31875          return;
31876        } // unknown tag type
31877
31878
31879        _this2.trigger('data', {
31880          type: 'tag',
31881          data: newLine.slice(4)
31882        });
31883      });
31884    }
31885    /**
31886     * Add a parser for custom headers
31887     *
31888     * @param {Object}   options              a map of options for the added parser
31889     * @param {RegExp}   options.expression   a regular expression to match the custom header
31890     * @param {string}   options.customType   the custom type to register to the output
31891     * @param {Function} [options.dataParser] function to parse the line into an object
31892     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
31893     */
31894    ;
31895
31896    _proto.addParser = function addParser(_ref) {
31897      var _this3 = this;
31898
31899      var expression = _ref.expression,
31900          customType = _ref.customType,
31901          dataParser = _ref.dataParser,
31902          segment = _ref.segment;
31903
31904      if (typeof dataParser !== 'function') {
31905        dataParser = function dataParser(line) {
31906          return line;
31907        };
31908      }
31909
31910      this.customParsers.push(function (line) {
31911        var match = expression.exec(line);
31912
31913        if (match) {
31914          _this3.trigger('data', {
31915            type: 'custom',
31916            data: dataParser(line),
31917            customType: customType,
31918            segment: segment
31919          });
31920
31921          return true;
31922        }
31923      });
31924    }
31925    /**
31926     * Add a custom header mapper
31927     *
31928     * @param {Object}   options
31929     * @param {RegExp}   options.expression   a regular expression to match the custom header
31930     * @param {Function} options.map          function to translate tag into a different tag
31931     */
31932    ;
31933
31934    _proto.addTagMapper = function addTagMapper(_ref2) {
31935      var expression = _ref2.expression,
31936          map = _ref2.map;
31937
31938      var mapFn = function mapFn(line) {
31939        if (expression.test(line)) {
31940          return map(line);
31941        }
31942
31943        return line;
31944      };
31945
31946      this.tagMappers.push(mapFn);
31947    };
31948
31949    return ParseStream;
31950  }(Stream);
31951
31952  var camelCase = function camelCase(str) {
31953    return str.toLowerCase().replace(/-(\w)/g, function (a) {
31954      return a[1].toUpperCase();
31955    });
31956  };
31957
31958  var camelCaseKeys = function camelCaseKeys(attributes) {
31959    var result = {};
31960    Object.keys(attributes).forEach(function (key) {
31961      result[camelCase(key)] = attributes[key];
31962    });
31963    return result;
31964  }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
31965  // we need this helper because defaults are based upon targetDuration and
31966  // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
31967  // target durations are set.
31968
31969
31970  var setHoldBack = function setHoldBack(manifest) {
31971    var serverControl = manifest.serverControl,
31972        targetDuration = manifest.targetDuration,
31973        partTargetDuration = manifest.partTargetDuration;
31974
31975    if (!serverControl) {
31976      return;
31977    }
31978
31979    var tag = '#EXT-X-SERVER-CONTROL';
31980    var hb = 'holdBack';
31981    var phb = 'partHoldBack';
31982    var minTargetDuration = targetDuration && targetDuration * 3;
31983    var minPartDuration = partTargetDuration && partTargetDuration * 2;
31984
31985    if (targetDuration && !serverControl.hasOwnProperty(hb)) {
31986      serverControl[hb] = minTargetDuration;
31987      this.trigger('info', {
31988        message: tag + " defaulting HOLD-BACK to targetDuration * 3 (" + minTargetDuration + ")."
31989      });
31990    }
31991
31992    if (minTargetDuration && serverControl[hb] < minTargetDuration) {
31993      this.trigger('warn', {
31994        message: tag + " clamping HOLD-BACK (" + serverControl[hb] + ") to targetDuration * 3 (" + minTargetDuration + ")"
31995      });
31996      serverControl[hb] = minTargetDuration;
31997    } // default no part hold back to part target duration * 3
31998
31999
32000    if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
32001      serverControl[phb] = partTargetDuration * 3;
32002      this.trigger('info', {
32003        message: tag + " defaulting PART-HOLD-BACK to partTargetDuration * 3 (" + serverControl[phb] + ")."
32004      });
32005    } // if part hold back is too small default it to part target duration * 2
32006
32007
32008    if (partTargetDuration && serverControl[phb] < minPartDuration) {
32009      this.trigger('warn', {
32010        message: tag + " clamping PART-HOLD-BACK (" + serverControl[phb] + ") to partTargetDuration * 2 (" + minPartDuration + ")."
32011      });
32012      serverControl[phb] = minPartDuration;
32013    }
32014  };
32015  /**
32016   * A parser for M3U8 files. The current interpretation of the input is
32017   * exposed as a property `manifest` on parser objects. It's just two lines to
32018   * create and parse a manifest once you have the contents available as a string:
32019   *
32020   * ```js
32021   * var parser = new m3u8.Parser();
32022   * parser.push(xhr.responseText);
32023   * ```
32024   *
32025   * New input can later be applied to update the manifest object by calling
32026   * `push` again.
32027   *
32028   * The parser attempts to create a usable manifest object even if the
32029   * underlying input is somewhat nonsensical. It emits `info` and `warning`
32030   * events during the parse if it encounters input that seems invalid or
32031   * requires some property of the manifest object to be defaulted.
32032   *
32033   * @class Parser
32034   * @extends Stream
32035   */
32036
32037
32038  var Parser = /*#__PURE__*/function (_Stream) {
32039    inheritsLoose(Parser, _Stream);
32040
32041    function Parser() {
32042      var _this;
32043
32044      _this = _Stream.call(this) || this;
32045      _this.lineStream = new LineStream();
32046      _this.parseStream = new ParseStream();
32047
32048      _this.lineStream.pipe(_this.parseStream);
32049      /* eslint-disable consistent-this */
32050
32051
32052      var self = assertThisInitialized(_this);
32053      /* eslint-enable consistent-this */
32054
32055
32056      var uris = [];
32057      var currentUri = {}; // if specified, the active EXT-X-MAP definition
32058
32059      var currentMap; // if specified, the active decryption key
32060
32061      var _key;
32062
32063      var hasParts = false;
32064
32065      var noop = function noop() {};
32066
32067      var defaultMediaGroups = {
32068        'AUDIO': {},
32069        'VIDEO': {},
32070        'CLOSED-CAPTIONS': {},
32071        'SUBTITLES': {}
32072      }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
32073      // used in MPDs with Widevine encrypted streams.
32074
32075      var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
32076
32077      var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
32078
32079      _this.manifest = {
32080        allowCache: true,
32081        discontinuityStarts: [],
32082        segments: []
32083      }; // keep track of the last seen segment's byte range end, as segments are not required
32084      // to provide the offset, in which case it defaults to the next byte after the
32085      // previous segment
32086
32087      var lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
32088
32089      var lastPartByterangeEnd = 0;
32090
32091      _this.on('end', function () {
32092        // only add preloadSegment if we don't yet have a uri for it.
32093        // and we actually have parts/preloadHints
32094        if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
32095          return;
32096        }
32097
32098        if (!currentUri.map && currentMap) {
32099          currentUri.map = currentMap;
32100        }
32101
32102        if (!currentUri.key && _key) {
32103          currentUri.key = _key;
32104        }
32105
32106        if (!currentUri.timeline && typeof currentTimeline === 'number') {
32107          currentUri.timeline = currentTimeline;
32108        }
32109
32110        _this.manifest.preloadSegment = currentUri;
32111      }); // update the manifest with the m3u8 entry from the parse stream
32112
32113
32114      _this.parseStream.on('data', function (entry) {
32115        var mediaGroup;
32116        var rendition;
32117        ({
32118          tag: function tag() {
32119            // switch based on the tag type
32120            (({
32121              version: function version() {
32122                if (entry.version) {
32123                  this.manifest.version = entry.version;
32124                }
32125              },
32126              'allow-cache': function allowCache() {
32127                this.manifest.allowCache = entry.allowed;
32128
32129                if (!('allowed' in entry)) {
32130                  this.trigger('info', {
32131                    message: 'defaulting allowCache to YES'
32132                  });
32133                  this.manifest.allowCache = true;
32134                }
32135              },
32136              byterange: function byterange() {
32137                var byterange = {};
32138
32139                if ('length' in entry) {
32140                  currentUri.byterange = byterange;
32141                  byterange.length = entry.length;
32142
32143                  if (!('offset' in entry)) {
32144                    /*
32145                     * From the latest spec (as of this writing):
32146                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
32147                     *
32148                     * Same text since EXT-X-BYTERANGE's introduction in draft 7:
32149                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
32150                     *
32151                     * "If o [offset] is not present, the sub-range begins at the next byte
32152                     * following the sub-range of the previous media segment."
32153                     */
32154                    entry.offset = lastByterangeEnd;
32155                  }
32156                }
32157
32158                if ('offset' in entry) {
32159                  currentUri.byterange = byterange;
32160                  byterange.offset = entry.offset;
32161                }
32162
32163                lastByterangeEnd = byterange.offset + byterange.length;
32164              },
32165              endlist: function endlist() {
32166                this.manifest.endList = true;
32167              },
32168              inf: function inf() {
32169                if (!('mediaSequence' in this.manifest)) {
32170                  this.manifest.mediaSequence = 0;
32171                  this.trigger('info', {
32172                    message: 'defaulting media sequence to zero'
32173                  });
32174                }
32175
32176                if (!('discontinuitySequence' in this.manifest)) {
32177                  this.manifest.discontinuitySequence = 0;
32178                  this.trigger('info', {
32179                    message: 'defaulting discontinuity sequence to zero'
32180                  });
32181                }
32182
32183                if (entry.duration > 0) {
32184                  currentUri.duration = entry.duration;
32185                }
32186
32187                if (entry.duration === 0) {
32188                  currentUri.duration = 0.01;
32189                  this.trigger('info', {
32190                    message: 'updating zero segment duration to a small value'
32191                  });
32192                }
32193
32194                this.manifest.segments = uris;
32195              },
32196              key: function key() {
32197                if (!entry.attributes) {
32198                  this.trigger('warn', {
32199                    message: 'ignoring key declaration without attribute list'
32200                  });
32201                  return;
32202                } // clear the active encryption key
32203
32204
32205                if (entry.attributes.METHOD === 'NONE') {
32206                  _key = null;
32207                  return;
32208                }
32209
32210                if (!entry.attributes.URI) {
32211                  this.trigger('warn', {
32212                    message: 'ignoring key declaration without URI'
32213                  });
32214                  return;
32215                }
32216
32217                if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
32218                  this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
32219
32220                  this.manifest.contentProtection['com.apple.fps.1_0'] = {
32221                    attributes: entry.attributes
32222                  };
32223                  return;
32224                } // check if the content is encrypted for Widevine
32225                // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
32226
32227
32228                if (entry.attributes.KEYFORMAT === widevineUuid) {
32229                  var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
32230
32231                  if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
32232                    this.trigger('warn', {
32233                      message: 'invalid key method provided for Widevine'
32234                    });
32235                    return;
32236                  }
32237
32238                  if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
32239                    this.trigger('warn', {
32240                      message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
32241                    });
32242                  }
32243
32244                  if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
32245                    this.trigger('warn', {
32246                      message: 'invalid key URI provided for Widevine'
32247                    });
32248                    return;
32249                  }
32250
32251                  if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
32252                    this.trigger('warn', {
32253                      message: 'invalid key ID provided for Widevine'
32254                    });
32255                    return;
32256                  } // if Widevine key attributes are valid, store them as `contentProtection`
32257                  // on the manifest to emulate Widevine tag structure in a DASH mpd
32258
32259
32260                  this.manifest.contentProtection = this.manifest.contentProtection || {};
32261                  this.manifest.contentProtection['com.widevine.alpha'] = {
32262                    attributes: {
32263                      schemeIdUri: entry.attributes.KEYFORMAT,
32264                      // remove '0x' from the key id string
32265                      keyId: entry.attributes.KEYID.substring(2)
32266                    },
32267                    // decode the base64-encoded PSSH box
32268                    pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
32269                  };
32270                  return;
32271                }
32272
32273                if (!entry.attributes.METHOD) {
32274                  this.trigger('warn', {
32275                    message: 'defaulting key method to AES-128'
32276                  });
32277                } // setup an encryption key for upcoming segments
32278
32279
32280                _key = {
32281                  method: entry.attributes.METHOD || 'AES-128',
32282                  uri: entry.attributes.URI
32283                };
32284
32285                if (typeof entry.attributes.IV !== 'undefined') {
32286                  _key.iv = entry.attributes.IV;
32287                }
32288              },
32289              'media-sequence': function mediaSequence() {
32290                if (!isFinite(entry.number)) {
32291                  this.trigger('warn', {
32292                    message: 'ignoring invalid media sequence: ' + entry.number
32293                  });
32294                  return;
32295                }
32296
32297                this.manifest.mediaSequence = entry.number;
32298              },
32299              'discontinuity-sequence': function discontinuitySequence() {
32300                if (!isFinite(entry.number)) {
32301                  this.trigger('warn', {
32302                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
32303                  });
32304                  return;
32305                }
32306
32307                this.manifest.discontinuitySequence = entry.number;
32308                currentTimeline = entry.number;
32309              },
32310              'playlist-type': function playlistType() {
32311                if (!/VOD|EVENT/.test(entry.playlistType)) {
32312                  this.trigger('warn', {
32313                    message: 'ignoring unknown playlist type: ' + entry.playlist
32314                  });
32315                  return;
32316                }
32317
32318                this.manifest.playlistType = entry.playlistType;
32319              },
32320              map: function map() {
32321                currentMap = {};
32322
32323                if (entry.uri) {
32324                  currentMap.uri = entry.uri;
32325                }
32326
32327                if (entry.byterange) {
32328                  currentMap.byterange = entry.byterange;
32329                }
32330
32331                if (_key) {
32332                  currentMap.key = _key;
32333                }
32334              },
32335              'stream-inf': function streamInf() {
32336                this.manifest.playlists = uris;
32337                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
32338
32339                if (!entry.attributes) {
32340                  this.trigger('warn', {
32341                    message: 'ignoring empty stream-inf attributes'
32342                  });
32343                  return;
32344                }
32345
32346                if (!currentUri.attributes) {
32347                  currentUri.attributes = {};
32348                }
32349
32350                _extends_1(currentUri.attributes, entry.attributes);
32351              },
32352              media: function media() {
32353                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
32354
32355                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
32356                  this.trigger('warn', {
32357                    message: 'ignoring incomplete or missing media group'
32358                  });
32359                  return;
32360                } // find the media group, creating defaults as necessary
32361
32362
32363                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
32364                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
32365                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
32366
32367                rendition = {
32368                  "default": /yes/i.test(entry.attributes.DEFAULT)
32369                };
32370
32371                if (rendition["default"]) {
32372                  rendition.autoselect = true;
32373                } else {
32374                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
32375                }
32376
32377                if (entry.attributes.LANGUAGE) {
32378                  rendition.language = entry.attributes.LANGUAGE;
32379                }
32380
32381                if (entry.attributes.URI) {
32382                  rendition.uri = entry.attributes.URI;
32383                }
32384
32385                if (entry.attributes['INSTREAM-ID']) {
32386                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
32387                }
32388
32389                if (entry.attributes.CHARACTERISTICS) {
32390                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
32391                }
32392
32393                if (entry.attributes.FORCED) {
32394                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
32395                } // insert the new rendition
32396
32397
32398                mediaGroup[entry.attributes.NAME] = rendition;
32399              },
32400              discontinuity: function discontinuity() {
32401                currentTimeline += 1;
32402                currentUri.discontinuity = true;
32403                this.manifest.discontinuityStarts.push(uris.length);
32404              },
32405              'program-date-time': function programDateTime() {
32406                if (typeof this.manifest.dateTimeString === 'undefined') {
32407                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
32408                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
32409                  // to the manifest object
32410                  // TODO: Consider removing this in future major version
32411                  this.manifest.dateTimeString = entry.dateTimeString;
32412                  this.manifest.dateTimeObject = entry.dateTimeObject;
32413                }
32414
32415                currentUri.dateTimeString = entry.dateTimeString;
32416                currentUri.dateTimeObject = entry.dateTimeObject;
32417              },
32418              targetduration: function targetduration() {
32419                if (!isFinite(entry.duration) || entry.duration < 0) {
32420                  this.trigger('warn', {
32421                    message: 'ignoring invalid target duration: ' + entry.duration
32422                  });
32423                  return;
32424                }
32425
32426                this.manifest.targetDuration = entry.duration;
32427                setHoldBack.call(this, this.manifest);
32428              },
32429              start: function start() {
32430                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
32431                  this.trigger('warn', {
32432                    message: 'ignoring start declaration without appropriate attribute list'
32433                  });
32434                  return;
32435                }
32436
32437                this.manifest.start = {
32438                  timeOffset: entry.attributes['TIME-OFFSET'],
32439                  precise: entry.attributes.PRECISE
32440                };
32441              },
32442              'cue-out': function cueOut() {
32443                currentUri.cueOut = entry.data;
32444              },
32445              'cue-out-cont': function cueOutCont() {
32446                currentUri.cueOutCont = entry.data;
32447              },
32448              'cue-in': function cueIn() {
32449                currentUri.cueIn = entry.data;
32450              },
32451              'skip': function skip() {
32452                this.manifest.skip = camelCaseKeys(entry.attributes);
32453                this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
32454              },
32455              'part': function part() {
32456                var _this2 = this;
32457
32458                hasParts = true; // parts are always specifed before a segment
32459
32460                var segmentIndex = this.manifest.segments.length;
32461                var part = camelCaseKeys(entry.attributes);
32462                currentUri.parts = currentUri.parts || [];
32463                currentUri.parts.push(part);
32464
32465                if (part.byterange) {
32466                  if (!part.byterange.hasOwnProperty('offset')) {
32467                    part.byterange.offset = lastPartByterangeEnd;
32468                  }
32469
32470                  lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
32471                }
32472
32473                var partIndex = currentUri.parts.length - 1;
32474                this.warnOnMissingAttributes_("#EXT-X-PART #" + partIndex + " for segment #" + segmentIndex, entry.attributes, ['URI', 'DURATION']);
32475
32476                if (this.manifest.renditionReports) {
32477                  this.manifest.renditionReports.forEach(function (r, i) {
32478                    if (!r.hasOwnProperty('lastPart')) {
32479                      _this2.trigger('warn', {
32480                        message: "#EXT-X-RENDITION-REPORT #" + i + " lacks required attribute(s): LAST-PART"
32481                      });
32482                    }
32483                  });
32484                }
32485              },
32486              'server-control': function serverControl() {
32487                var attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
32488
32489                if (!attrs.hasOwnProperty('canBlockReload')) {
32490                  attrs.canBlockReload = false;
32491                  this.trigger('info', {
32492                    message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
32493                  });
32494                }
32495
32496                setHoldBack.call(this, this.manifest);
32497
32498                if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
32499                  this.trigger('warn', {
32500                    message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
32501                  });
32502                }
32503              },
32504              'preload-hint': function preloadHint() {
32505                // parts are always specifed before a segment
32506                var segmentIndex = this.manifest.segments.length;
32507                var hint = camelCaseKeys(entry.attributes);
32508                var isPart = hint.type && hint.type === 'PART';
32509                currentUri.preloadHints = currentUri.preloadHints || [];
32510                currentUri.preloadHints.push(hint);
32511
32512                if (hint.byterange) {
32513                  if (!hint.byterange.hasOwnProperty('offset')) {
32514                    // use last part byterange end or zero if not a part.
32515                    hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
32516
32517                    if (isPart) {
32518                      lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
32519                    }
32520                  }
32521                }
32522
32523                var index = currentUri.preloadHints.length - 1;
32524                this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex, entry.attributes, ['TYPE', 'URI']);
32525
32526                if (!hint.type) {
32527                  return;
32528                } // search through all preload hints except for the current one for
32529                // a duplicate type.
32530
32531
32532                for (var i = 0; i < currentUri.preloadHints.length - 1; i++) {
32533                  var otherHint = currentUri.preloadHints[i];
32534
32535                  if (!otherHint.type) {
32536                    continue;
32537                  }
32538
32539                  if (otherHint.type === hint.type) {
32540                    this.trigger('warn', {
32541                      message: "#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex + " has the same TYPE " + hint.type + " as preload hint #" + i
32542                    });
32543                  }
32544                }
32545              },
32546              'rendition-report': function renditionReport() {
32547                var report = camelCaseKeys(entry.attributes);
32548                this.manifest.renditionReports = this.manifest.renditionReports || [];
32549                this.manifest.renditionReports.push(report);
32550                var index = this.manifest.renditionReports.length - 1;
32551                var required = ['LAST-MSN', 'URI'];
32552
32553                if (hasParts) {
32554                  required.push('LAST-PART');
32555                }
32556
32557                this.warnOnMissingAttributes_("#EXT-X-RENDITION-REPORT #" + index, entry.attributes, required);
32558              },
32559              'part-inf': function partInf() {
32560                this.manifest.partInf = camelCaseKeys(entry.attributes);
32561                this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
32562
32563                if (this.manifest.partInf.partTarget) {
32564                  this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
32565                }
32566
32567                setHoldBack.call(this, this.manifest);
32568              }
32569            })[entry.tagType] || noop).call(self);
32570          },
32571          uri: function uri() {
32572            currentUri.uri = entry.uri;
32573            uris.push(currentUri); // if no explicit duration was declared, use the target duration
32574
32575            if (this.manifest.targetDuration && !('duration' in currentUri)) {
32576              this.trigger('warn', {
32577                message: 'defaulting segment duration to the target duration'
32578              });
32579              currentUri.duration = this.manifest.targetDuration;
32580            } // annotate with encryption information, if necessary
32581
32582
32583            if (_key) {
32584              currentUri.key = _key;
32585            }
32586
32587            currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
32588
32589            if (currentMap) {
32590              currentUri.map = currentMap;
32591            }
32591 // reset the last byterange end as it needs to be 0 between parts
32592
32593
32594            lastPartByterangeEnd = 0; // prepare for the next URI
32595
32596            currentUri = {};
32597          },
32598          comment: function comment() {// comments are not important for playback
32599          },
32600          custom: function custom() {
32601            // if this is segment-level data attach the output to the segment
32602            if (entry.segment) {
32603              currentUri.custom = currentUri.custom || {};
32604              currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
32605            } else {
32606              this.manifest.custom = this.manifest.custom || {};
32607              this.manifest.custom[entry.customType] = entry.data;
32608            }
32609          }
32610        })[entry.type].call(self);
32611      });
32612
32613      return _this;
32614    }
32615
32616    var _proto = Parser.prototype;
32617
32618    _proto.warnOnMissingAttributes_ = function warnOnMissingAttributes_(identifier, attributes, required) {
32619      var missing = [];
32620      required.forEach(function (key) {
32621        if (!attributes.hasOwnProperty(key)) {
32622          missing.push(key);
32623        }
32624      });
32625
32626      if (missing.length) {
32627        this.trigger('warn', {
32628          message: identifier + " lacks required attribute(s): " + missing.join(', ')
32629        });
32630      }
32631    }
32632    /**
32633     * Parse the input string and update the manifest object.
32634     *
32635     * @param {string} chunk a potentially incomplete portion of the manifest
32636     */
32637    ;
32638
32639    _proto.push = function push(chunk) {
32640      this.lineStream.push(chunk);
32641    }
32642    /**
32643     * Flush any remaining input. This can be handy if the last line of an M3U8
32644     * manifest did not contain a trailing newline but the file has been
32645     * completely received.
32646     */
32647    ;
32648
32649    _proto.end = function end() {
32650      // flush any buffered input
32651      this.lineStream.push('\n');
32652      this.trigger('end');
32653    }
32654    /**
32655     * Add an additional parser for non-standard tags
32656     *
32657     * @param {Object}   options              a map of options for the added parser
32658     * @param {RegExp}   options.expression   a regular expression to match the custom header
32659     * @param {string}   options.type         the type to register to the output
32660     * @param {Function} [options.dataParser] function to parse the line into an object
32661     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
32662     */
32663    ;
32664
32665    _proto.addParser = function addParser(options) {
32666      this.parseStream.addParser(options);
32667    }
32668    /**
32669     * Add a custom header mapper
32670     *
32671     * @param {Object}   options
32672     * @param {RegExp}   options.expression   a regular expression to match the custom header
32673     * @param {Function} options.map          function to translate tag into a different tag
32674     */
32675    ;
32676
32677    _proto.addTagMapper = function addTagMapper(options) {
32678      this.parseStream.addTagMapper(options);
32679    };
32680
32681    return Parser;
32682  }(Stream);
32683
32684  var regexs = {
32685    // to determine mime types
32686    mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
32687    webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
32688    ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
32689    // to determine if a codec is audio or video
32690    video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
32691    audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
32692    text: /^(stpp.ttml.im1t)/,
32693    // mux.js support regex
32694    muxerVideo: /^(avc0?1)/,
32695    muxerAudio: /^(mp4a)/,
32696    // match nothing as muxer does not support text right now.
32697    // there cannot never be a character before the start of a string
32698    // so this matches nothing.
32699    muxerText: /a^/
32700  };
32701  var mediaTypes = ['video', 'audio', 'text'];
32702  var upperMediaTypes = ['Video', 'Audio', 'Text'];
32703  /**
32704   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
32705   * `avc1.<hhhhhh>`
32706   *
32707   * @param {string} codec
32708   *        Codec string to translate
32709   * @return {string}
32710   *         The translated codec string
32711   */
32712
32713  var translateLegacyCodec = function translateLegacyCodec(codec) {
32714    if (!codec) {
32715      return codec;
32716    }
32717
32718    return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
32719      var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
32720      var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
32721      return 'avc1.' + profileHex + '00' + avcLevelHex;
32722    });
vendor: 4,171 bytes, lines 32723-32868
32723  };
32724  /**
32725   * @typedef {Object} ParsedCodecInfo
32726   * @property {number} codecCount
32727   *           Number of codecs parsed
32728   * @property {string} [videoCodec]
32729   *           Parsed video codec (if found)
32730   * @property {string} [videoObjectTypeIndicator]
32731   *           Video object type indicator (if found)
32732   * @property {string|null} audioProfile
32733   *           Audio profile
32734   */
32735
32736  /**
32737   * Parses a codec string to retrieve the number of codecs specified, the video codec and
32738   * object type indicator, and the audio profile.
32739   *
32740   * @param {string} [codecString]
32741   *        The codec string to parse
32742   * @return {ParsedCodecInfo}
32743   *         Parsed codec info
32744   */
32745
32746  var parseCodecs = function parseCodecs(codecString) {
32747    if (codecString === void 0) {
32748      codecString = '';
32749    }
32750
32751    var codecs = codecString.split(',');
32752    var result = [];
32753    codecs.forEach(function (codec) {
32754      codec = codec.trim();
32755      var codecType;
32756      mediaTypes.forEach(function (name) {
32757        var match = regexs[name].exec(codec.toLowerCase());
32758
32759        if (!match || match.length <= 1) {
32760          return;
32761        }
32762
32763        codecType = name; // maintain codec case
32764
32765        var type = codec.substring(0, match[1].length);
32766        var details = codec.replace(type, '');
32767        result.push({
32768          type: type,
32769          details: details,
32770          mediaType: name
32771        });
32772      });
32773
32774      if (!codecType) {
32775        result.push({
32776          type: codec,
32777          details: '',
32778          mediaType: 'unknown'
32779        });
32780      }
32781    });
32782    return result;
32783  };
32784  /**
32785   * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
32786   * a default alternate audio playlist for the provided audio group.
32787   *
32788   * @param {Object} master
32789   *        The master playlist
32790   * @param {string} audioGroupId
32791   *        ID of the audio group for which to find the default codec info
32792   * @return {ParsedCodecInfo}
32793   *         Parsed codec info
32794   */
32795
32796  var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
32797    if (!master.mediaGroups.AUDIO || !audioGroupId) {
32798      return null;
32799    }
32800
32801    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
32802
32803    if (!audioGroup) {
32804      return null;
32805    }
32806
32807    for (var name in audioGroup) {
32808      var audioType = audioGroup[name];
32809
32810      if (audioType["default"] && audioType.playlists) {
32811        // codec should be the same for all playlists within the audio type
32812        return parseCodecs(audioType.playlists[0].attributes.CODECS);
32813      }
32814    }
32815
32816    return null;
32817  };
32818  var isAudioCodec = function isAudioCodec(codec) {
32819    if (codec === void 0) {
32820      codec = '';
32821    }
32822
32823    return regexs.audio.test(codec.trim().toLowerCase());
32824  };
32825  var isTextCodec = function isTextCodec(codec) {
32826    if (codec === void 0) {
32827      codec = '';
32828    }
32829
32830    return regexs.text.test(codec.trim().toLowerCase());
32831  };
32832  var getMimeForCodec = function getMimeForCodec(codecString) {
32833    if (!codecString || typeof codecString !== 'string') {
32834      return;
32835    }
32836
32837    var codecs = codecString.toLowerCase().split(',').map(function (c) {
32838      return translateLegacyCodec(c.trim());
32839    }); // default to video type
32840
32841    var type = 'video'; // only change to audio type if the only codec we have is
32842    // audio
32843
32844    if (codecs.length === 1 && isAudioCodec(codecs[0])) {
32845      type = 'audio';
32846    } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
32847      // text uses application/<container> for now
32848      type = 'application';
32849    } // default the container to mp4
32850
32851
32852    var container = 'mp4'; // every codec must be able to go into the container
32853    // for that container to be the correct one
32854
32855    if (codecs.every(function (c) {
32856      return regexs.mp4.test(c);
32857    })) {
32858      container = 'mp4';
32859    } else if (codecs.every(function (c) {
32860      return regexs.webm.test(c);
32861    })) {
32862      container = 'webm';
32863    } else if (codecs.every(function (c) {
32864      return regexs.ogg.test(c);
32865    })) {
32866      container = 'ogg';
32867    }
32868
32869    return type + "/" + container + ";codecs=\"" + codecString + "\"";
32870  };
32871  var browserSupportsCodec = function browserSupportsCodec(codecString) {
32872    if (codecString === void 0) {
32873      codecString = '';
32874    }
32875
32876    return window_1.MediaSource && window_1.MediaSource.isTypeSupported && window_1.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
32877  };
32878  var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
32879    if (codecString === void 0) {
32880      codecString = '';
32881    }
32882
32883    return codecString.toLowerCase().split(',').every(function (codec) {
32884      codec = codec.trim(); // any match is supported.
32885
32886      for (var i = 0; i < upperMediaTypes.length; i++) {
32887        var type = upperMediaTypes[i];
32888
32889        if (regexs["muxer" + type].test(codec)) {
32890          return true;
32891        }
32892      }
32893
32894      return false;
32895    });
32896  };
32897  var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
32898  var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
32899
32900  var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
32901  var DASH_REGEX = /^application\/dash\+xml/i;
32902  /**
32903   * Returns a string that describes the type of source based on a video source object's
32904   * media type.
32905   *
32906   * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
32907   *
32908   * @param {string} type
32909   *        Video source object media type
32910   * @return {('hls'|'dash'|'vhs-json'|null)}
32911   *         VHS source type string
32912   */
32913
32914  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
32915    if (MPEGURL_REGEX.test(type)) {
32916      return 'hls';
32917    }
32918
32919    if (DASH_REGEX.test(type)) {
32920      return 'dash';
32921    } // Denotes the special case of a manifest object passed to http-streaming instead of a
32922    // source URL.
32923    //
32924    // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
32925    //
32926    // In this case, vnd stands for vendor, video.js for the organization, VHS for this
32927    // project, and the +json suffix identifies the structure of the media type.
32928
32929
32930    if (type === 'application/vnd.videojs.vhs+json') {
32931      return 'vhs-json';
32932    }
32933
32934    return null;
32935  };
32936
32937  /**
32938   * "Shallow freezes" an object to render it immutable.
32939   * Uses `Object.freeze` if available,
32940   * otherwise the immutability is only in the type.
32941   *
32942   * Is used to create "enum like" objects.
32943   *
32944   * @template T
32945   * @param {T} object the object to freeze
32946   * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
32947   * 				allows to inject custom object constructor for tests
32948   * @returns {Readonly<T>}
32949   *
32950   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
32951   */
32952
32953  function freeze(object, oc) {
32954    if (oc === undefined) {
32955      oc = Object;
32956    }
32957
32958    return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object;
32959  }
32960  /**
32961   * All mime types that are allowed as input to `DOMParser.parseFromString`
32962   *
32963   * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
32964   * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
32965   * @see DOMParser.prototype.parseFromString
32966   */
32967
32968
32969  var MIME_TYPE = freeze({
32970    /**
32971     * `text/html`, the only mime type that triggers treating an XML document as HTML.
32972     *
32973     * @see DOMParser.SupportedType.isHTML
32974     * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
32975     * @see https://en.wikipedia.org/wiki/HTML Wikipedia
32976     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
32977     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
32978     */
32979    HTML: 'text/html',
32980
32981    /**
32982     * Helper method to check a mime type if it indicates an HTML document
32983     *
32984     * @param {string} [value]
32985     * @returns {boolean}
32986     *
32987     * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
32988     * @see https://en.wikipedia.org/wiki/HTML Wikipedia
32989     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
32990     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring 	 */
32991    isHTML: function isHTML(value) {
32992      return value === MIME_TYPE.HTML;
32993    },
32994
32995    /**
32996     * `application/xml`, the standard mime type for XML documents.
32997     *
32998     * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
32999     * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
33000     * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
33001     */
33002    XML_APPLICATION: 'application/xml',
33003
33004    /**
33005     * `text/html`, an alias for `application/xml`.
33006     *
33007     * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
33008     * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
33009     * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
33010     */
33011    XML_TEXT: 'text/xml',
33012
33013    /**
33014     * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
33015     * but is parsed as an XML document.
33016     *
33017     * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
33018     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
33019     * @see https://en.wikipedia.org/wiki/XHTML Wikipedia
33020     */
33021    XML_XHTML_APPLICATION: 'application/xhtml+xml',
33022
33023    /**
33024     * `image/svg+xml`,
33025     *
33026     * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
33027     * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
33028     * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
33029     */
33030    XML_SVG_IMAGE: 'image/svg+xml'
33031  });
33032  /**
33033   * Namespaces that are used in this code base.
33034   *
33035   * @see http://www.w3.org/TR/REC-xml-names
33036   */
33037
33038  var NAMESPACE$2 = freeze({
33039    /**
33040     * The XHTML namespace.
33041     *
33042     * @see http://www.w3.org/1999/xhtml
33043     */
33044    HTML: 'http://www.w3.org/1999/xhtml',
33045
33046    /**
33047     * Checks if `uri` equals `NAMESPACE.HTML`.
33048     *
33049     * @param {string} [uri]
33050     *
33051     * @see NAMESPACE.HTML
33052     */
33053    isHTML: function isHTML(uri) {
33054      return uri === NAMESPACE$2.HTML;
33055    },
33056
33057    /**
33058     * The SVG namespace.
33059     *
33060     * @see http://www.w3.org/2000/svg
33061     */
33062    SVG: 'http://www.w3.org/2000/svg',
33063
33064    /**
33065     * The `xml:` namespace.
33066     *
33067     * @see http://www.w3.org/XML/1998/namespace
33068     */
33069    XML: 'http://www.w3.org/XML/1998/namespace',
33070
33071    /**
33072     * The `xmlns:` namespace
33073     *
33074     * @see https://www.w3.org/2000/xmlns/
33075     */
33076    XMLNS: 'http://www.w3.org/2000/xmlns/'
33077  });
33078  var freeze_1 = freeze;
33079  var MIME_TYPE_1 = MIME_TYPE;
33080  var NAMESPACE_1 = NAMESPACE$2;
33081  var conventions = {
33082    freeze: freeze_1,
33083    MIME_TYPE: MIME_TYPE_1,
33084    NAMESPACE: NAMESPACE_1
33085  };
33086
33087  var entities = createCommonjsModule(function (module, exports) {
33088    var freeze = conventions.freeze;
33089    /**
33090     * The entities that are predefined in every XML document.
33091     *
33092     * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1
33093     * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0
33094     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia
33095     */
33096
33097    exports.XML_ENTITIES = freeze({
33098      amp: '&',
33099      apos: "'",
33100      gt: '>',
33101      lt: '<',
33102      quot: '"'
33103    });
33104    /**
33105     * A map of currently 241 entities that are detected in an HTML document.
33106     * They contain all entries from `XML_ENTITIES`.
33107     *
33108     * @see XML_ENTITIES
33109     * @see DOMParser.parseFromString
33110     * @see DOMImplementation.prototype.createHTMLDocument
33111     * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec
33112     * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names
33113     * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML
33114     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML)
33115     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML)
33116     */
33117
33118    exports.HTML_ENTITIES = freeze({
33119      lt: '<',
33120      gt: '>',
33121      amp: '&',
33122      quot: '"',
33123      apos: "'",
33124      Agrave: "À",
33125      Aacute: "Á",
33126      Acirc: "Â",
33127      Atilde: "Ã",
33128      Auml: "Ä",
33129      Aring: "Å",
33130      AElig: "Æ",
33131      Ccedil: "Ç",
33132      Egrave: "È",
33133      Eacute: "É",
33134      Ecirc: "Ê",
33135      Euml: "Ë",
33136      Igrave: "Ì",
33137      Iacute: "Í",
33138      Icirc: "Î",
33139      Iuml: "Ï",
33140      ETH: "Ð",
33141      Ntilde: "Ñ",
33142      Ograve: "Ò",
33143      Oacute: "Ó",
33144      Ocirc: "Ô",
33145      Otilde: "Õ",
33146      Ouml: "Ö",
33147      Oslash: "Ø",
33148      Ugrave: "Ù",
33149      Uacute: "Ú",
33150      Ucirc: "Û",
33151      Uuml: "Ü",
33152      Yacute: "Ý",
33153      THORN: "Þ",
33154      szlig: "ß",
33155      agrave: "à",
33156      aacute: "á",
33157      acirc: "â",
33158      atilde: "ã",
33159      auml: "ä",
33160      aring: "Ã¥",
33161      aelig: "æ",
33162      ccedil: "ç",
33163      egrave: "è",
33164      eacute: "é",
33165      ecirc: "ê",
33166      euml: "ë",
33167      igrave: "ì",
33168      iacute: "í",
33169      icirc: "î",
33170      iuml: "ï",
33171      eth: "ð",
33172      ntilde: "ñ",
33173      ograve: "ò",
33174      oacute: "ó",
33175      ocirc: "ô",
33176      otilde: "õ",
33177      ouml: "ö",
33178      oslash: "ø",
33179      ugrave: "ù",
33180      uacute: "ú",
33181      ucirc: "û",
33182      uuml: "ü",
33183      yacute: "ý",
33184      thorn: "þ",
33185      yuml: "ÿ",
33186      nbsp: "\xA0",
33187      iexcl: "¡",
33188      cent: "¢",
33189      pound: "£",
33190      curren: "¤",
33191      yen: "Â¥",
33192      brvbar: "¦",
33193      sect: "§",
33194      uml: "¨",
33195      copy: "©",
33196      ordf: "ª",
33197      laquo: "«",
33198      not: "¬",
33199      shy: "­­",
33200      reg: "®",
33201      macr: "¯",
33202      deg: "°",
33203      plusmn: "±",
33204      sup2: "²",
33205      sup3: "³",
33206      acute: "´",
33207      micro: "µ",
33208      para: "¶",
33209      middot: "·",
33210      cedil: "¸",
33211      sup1: "¹",
33212      ordm: "º",
33213      raquo: "»",
33214      frac14: "¼",
33215      frac12: "½",
33216      frac34: "¾",
33217      iquest: "¿",
33218      times: "×",
33219      divide: "÷",
33220      forall: "∀",
33221      part: "∂",
33222      exist: "∃",
33223      empty: "∅",
33224      nabla: "∇",
33225      isin: "∈",
33226      notin: "∉",
33227      ni: "∋",
33228      prod: "∏",
33229      sum: "∑",
33230      minus: "−",
33231      lowast: "∗",
33232      radic: "√",
33233      prop: "∝",
33234      infin: "∞",
33235      ang: "∠",
33236      and: "∧",
33237      or: "∨",
33238      cap: "∩",
33239      cup: "∪",
33240      'int': "∫",
33241      there4: "∴",
33242      sim: "∼",
33243      cong: "≅",
33244      asymp: "≈",
33245      ne: "≠",
33246      equiv: "≡",
33247      le: "≤",
33248      ge: "≥",
33249      sub: "⊂",
33250      sup: "⊃",
33251      nsub: "⊄",
33252      sube: "⊆",
33253      supe: "⊇",
33254      oplus: "⊕",
33255      otimes: "⊗",
33256      perp: "⊥",
33257      sdot: "⋅",
33258      Alpha: "Α",
33259      Beta: "Β",
33260      Gamma: "Γ",
33261      Delta: "Δ",
33262      Epsilon: "Ε",
33263      Zeta: "Ζ",
33264      Eta: "Η",
33265      Theta: "Θ",
33266      Iota: "Ι",
33267      Kappa: "Κ",
33268      Lambda: "Λ",
33269      Mu: "Μ",
33270      Nu: "Ν",
33271      Xi: "Ξ",
33272      Omicron: "Ο",
33273      Pi: "Π",
33274      Rho: "Ρ",
33275      Sigma: "Σ",
33276      Tau: "Τ",
33277      Upsilon: "Î¥",
33278      Phi: "Φ",
33279      Chi: "Χ",
33280      Psi: "Ψ",
33281      Omega: "Ω",
33282      alpha: "α",
33283      beta: "β",
33284      gamma: "γ",
33285      delta: "δ",
33286      epsilon: "ε",
33287      zeta: "ζ",
33288      eta: "η",
33289      theta: "θ",
33290      iota: "ι",
33291      kappa: "κ",
33292      lambda: "λ",
33293      mu: "μ",
33294      nu: "ν",
33295      xi: "ξ",
33296      omicron: "ο",
33297      pi: "π",
33298      rho: "ρ",
33299      sigmaf: "ς",
33300      sigma: "σ",
33301      tau: "τ",
33302      upsilon: "υ",
33303      phi: "φ",
33304      chi: "χ",
33305      psi: "ψ",
33306      omega: "ω",
33307      thetasym: "ϑ",
33308      upsih: "ϒ",
33309      piv: "ϖ",
33310      OElig: "Œ",
33311      oelig: "œ",
33312      Scaron: "Å ",
33313      scaron: "Å¡",
33314      Yuml: "Ÿ",
33315      fnof: "ƒ",
33316      circ: "ˆ",
33317      tilde: "˜",
33318      ensp: " ",
33319      emsp: " ",
33320      thinsp: " ",
33321      zwnj: "‌",
33322      zwj: "‍",
33323      lrm: "‎",
33324      rlm: "‏",
33325      ndash: "–",
33326      mdash: "—",
33327      lsquo: "‘",
33328      rsquo: "’",
33329      sbquo: "‚",
33330      ldquo: "“",
33331      rdquo: "”",
33332      bdquo: "„",
33333      dagger: "†",
33334      Dagger: "‡",
33335      bull: "•",
33336      hellip: "…",
33337      permil: "‰",
33338      prime: "′",
33339      Prime: "″",
33340      lsaquo: "‹",
33341      rsaquo: "›",
33342      oline: "‾",
33343      euro: "€",
33344      trade: "™",
33345      larr: "←",
33346      uarr: "↑",
33347      rarr: "→",
33348      darr: "↓",
33349      harr: "↔",
33350      crarr: "↵",
33351      lceil: "⌈",
33352      rceil: "⌉",
33353      lfloor: "⌊",
33354      rfloor: "⌋",
33355      loz: "◊",
33356      spades: "♠",
33357      clubs: "♣",
33358      hearts: "♥",
33359      diams: "♦"
33360    });
vendor: 4,817 bytes, lines 33361-33489
33361    /**
33362     * @deprecated use `HTML_ENTITIES` instead
33363     * @see HTML_ENTITIES
33364     */
33365
33366    exports.entityMap = exports.HTML_ENTITIES;
33367  });
33368  entities.XML_ENTITIES;
33369  entities.HTML_ENTITIES;
33370  entities.entityMap;
33371
33372  var NAMESPACE$1 = conventions.NAMESPACE; //[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]
33373  //[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
33374  //[5]   	Name	   ::=   	NameStartChar (NameChar)*
33375
33376  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
33377
33378  var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
33379  var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
33380  //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(',')
33381  //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
33382  //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
33383
33384  var S_TAG = 0; //tag name offerring
33385
33386  var S_ATTR = 1; //attr name offerring 
33387
33388  var S_ATTR_SPACE = 2; //attr name end and space offer
33389
33390  var S_EQ = 3; //=space?
33391
33392  var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
33393
33394  var S_ATTR_END = 5; //attr value end and no space(quot end)
33395
33396  var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
33397
33398  var S_TAG_CLOSE = 7; //closed el<el />
33399
33400  /**
33401   * Creates an error that will not be caught by XMLReader aka the SAX parser.
33402   *
33403   * @param {string} message
33404   * @param {any?} locator Optional, can provide details about the location in the source
33405   * @constructor
33406   */
33407
33408  function ParseError(message, locator) {
33409    this.message = message;
33410    this.locator = locator;
33411    if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError);
33412  }
33413
33414  ParseError.prototype = new Error();
33415  ParseError.prototype.name = ParseError.name;
33416
33417  function XMLReader() {}
33418
33419  XMLReader.prototype = {
33420    parse: function parse(source, defaultNSMap, entityMap) {
33421      var domBuilder = this.domBuilder;
33422      domBuilder.startDocument();
33423
33424      _copy(defaultNSMap, defaultNSMap = {});
33425
33426      _parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
33427
33428      domBuilder.endDocument();
33429    }
33430  };
33431
33432  function _parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
33433    function fixedFromCharCode(code) {
33434      // String.prototype.fromCharCode does not supports
33435      // > 2 bytes unicode chars directly
33436      if (code > 0xffff) {
33437        code -= 0x10000;
33438        var surrogate1 = 0xd800 + (code >> 10),
33439            surrogate2 = 0xdc00 + (code & 0x3ff);
33440        return String.fromCharCode(surrogate1, surrogate2);
33441      } else {
33442        return String.fromCharCode(code);
33443      }
33444    }
33445
33446    function entityReplacer(a) {
33447      var k = a.slice(1, -1);
33448
33449      if (k in entityMap) {
33450        return entityMap[k];
33451      } else if (k.charAt(0) === '#') {
33452        return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x')));
33453      } else {
33454        errorHandler.error('entity not found:' + a);
33455        return a;
33456      }
33457    }
33458
33459    function appendText(end) {
33460      //has some bugs
33461      if (end > start) {
33462        var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer);
33463        locator && position(start);
33464        domBuilder.characters(xt, 0, end - start);
33465        start = end;
33466      }
33467    }
33468
33469    function position(p, m) {
33470      while (p >= lineEnd && (m = linePattern.exec(source))) {
33471        lineStart = m.index;
33472        lineEnd = lineStart + m[0].length;
33473        locator.lineNumber++; //console.log('line++:',locator,startPos,endPos)
33474      }
33475
33476      locator.columnNumber = p - lineStart + 1;
33477    }
33478
33479    var lineStart = 0;
33480    var lineEnd = 0;
33481    var linePattern = /.*(?:\r\n?|\n)|.*$/g;
33482    var locator = domBuilder.locator;
33483    var parseStack = [{
33484      currentNSMap: defaultNSMapCopy
33485    }];
33486    var closeMap = {};
33487    var start = 0;
33488
33489    while (true) {
vendor: 16,124 bytes, lines 33490-34017
33490      try {
33491        var tagStart = source.indexOf('<', start);
33492
33493        if (tagStart < 0) {
33494          if (!source.substr(start).match(/^\s*$/)) {
33495            var doc = domBuilder.doc;
33496            var text = doc.createTextNode(source.substr(start));
33497            doc.appendChild(text);
33498            domBuilder.currentElement = text;
33499          }
33500
33501          return;
33502        }
33503
33504        if (tagStart > start) {
33505          appendText(tagStart);
33506        }
33507
33508        switch (source.charAt(tagStart + 1)) {
33509          case '/':
33510            var end = source.indexOf('>', tagStart + 3);
33511            var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');
33512            var config = parseStack.pop();
33513
33514            if (end < 0) {
33515              tagName = source.substring(tagStart + 2).replace(/[\s<].*/, '');
33516              errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName);
33517              end = tagStart + 1 + tagName.length;
33518            } else if (tagName.match(/\s</)) {
33519              tagName = tagName.replace(/[\s<].*/, '');
33520              errorHandler.error("end tag name: " + tagName + ' maybe not complete');
33521              end = tagStart + 1 + tagName.length;
33522            }
33523
33524            var localNSMap = config.localNSMap;
33525            var endMatch = config.tagName == tagName;
33526            var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase();
33527
33528            if (endIgnoreCaseMach) {
33529              domBuilder.endElement(config.uri, config.localName, tagName);
33530
33531              if (localNSMap) {
33532                for (var prefix in localNSMap) {
33533                  domBuilder.endPrefixMapping(prefix);
33534                }
33535              }
33536
33537              if (!endMatch) {
33538                errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); // No known test case
33539              }
33540            } else {
33541              parseStack.push(config);
33542            }
33543
33544            end++;
33545            break;
33546          // end elment
33547
33548          case '?':
33549            // <?...?>
33550            locator && position(tagStart);
33551            end = parseInstruction(source, tagStart, domBuilder);
33552            break;
33553
33554          case '!':
33555            // <!doctype,<![CDATA,<!--
33556            locator && position(tagStart);
33557            end = parseDCC(source, tagStart, domBuilder, errorHandler);
33558            break;
33559
33560          default:
33561            locator && position(tagStart);
33562            var el = new ElementAttributes();
33563            var currentNSMap = parseStack[parseStack.length - 1].currentNSMap; //elStartEnd
33564
33565            var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler);
33566            var len = el.length;
33567
33568            if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) {
33569              el.closed = true;
33570
33571              if (!entityMap.nbsp) {
33572                errorHandler.warning('unclosed xml attribute');
33573              }
33574            }
33575
33576            if (locator && len) {
33577              var locator2 = copyLocator(locator, {}); //try{//attribute position fixed
33578
33579              for (var i = 0; i < len; i++) {
33580                var a = el[i];
33581                position(a.offset);
33582                a.locator = copyLocator(locator, {});
33583              }
33584
33585              domBuilder.locator = locator2;
33586
33587              if (appendElement(el, domBuilder, currentNSMap)) {
33588                parseStack.push(el);
33589              }
33590
33591              domBuilder.locator = locator;
33592            } else {
33593              if (appendElement(el, domBuilder, currentNSMap)) {
33594                parseStack.push(el);
33595              }
33596            }
33597
33598            if (NAMESPACE$1.isHTML(el.uri) && !el.closed) {
33599              end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
33600            } else {
33601              end++;
33602            }
33603
33604        }
33605      } catch (e) {
33606        if (e instanceof ParseError) {
33607          throw e;
33608        }
33609
33610        errorHandler.error('element parse error: ' + e);
33611        end = -1;
33612      }
33613
33614      if (end > start) {
33615        start = end;
33616      } else {
33617        //TODO: 这里有可能sax回退,有位置错误风险
33618        appendText(Math.max(tagStart, start) + 1);
33619      }
33620    }
33621  }
33622
33623  function copyLocator(f, t) {
33624    t.lineNumber = f.lineNumber;
33625    t.columnNumber = f.columnNumber;
33626    return t;
33627  }
33628  /**
33629   * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
33630   * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
33631   */
33632
33633
33634  function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) {
33635    /**
33636     * @param {string} qname
33637     * @param {string} value
33638     * @param {number} startIndex
33639     */
33640    function addAttribute(qname, value, startIndex) {
33641      if (qname in el.attributeNames) errorHandler.fatalError('Attribute ' + qname + ' redefined');
33642      el.addValue(qname, value, startIndex);
33643    }
33644
33645    var attrName;
33646    var value;
33647    var p = ++start;
33648    var s = S_TAG; //status
33649
33650    while (true) {
33651      var c = source.charAt(p);
33652
33653      switch (c) {
33654        case '=':
33655          if (s === S_ATTR) {
33656            //attrName
33657            attrName = source.slice(start, p);
33658            s = S_EQ;
33659          } else if (s === S_ATTR_SPACE) {
33660            s = S_EQ;
33661          } else {
33662            //fatalError: equal must after attrName or space after attrName
33663            throw new Error('attribute equal must after attrName'); // No known test case
33664          }
33665
33666          break;
33667
33668        case '\'':
33669        case '"':
33670          if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
33671          ) {
33672              //equal
33673              if (s === S_ATTR) {
33674                errorHandler.warning('attribute value must after "="');
33675                attrName = source.slice(start, p);
33676              }
33677
33678              start = p + 1;
33679              p = source.indexOf(c, start);
33680
33681              if (p > 0) {
33682                value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer);
33683                addAttribute(attrName, value, start - 1);
33684                s = S_ATTR_END;
33685              } else {
33686                //fatalError: no end quot match
33687                throw new Error('attribute value no end \'' + c + '\' match');
33688              }
33689            } else if (s == S_ATTR_NOQUOT_VALUE) {
33690            value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); //console.log(attrName,value,start,p)
33691
33692            addAttribute(attrName, value, start); //console.dir(el)
33693
33694            errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
33695            start = p + 1;
33696            s = S_ATTR_END;
33697          } else {
33698            //fatalError: no equal before
33699            throw new Error('attribute value must after "="'); // No known test case
33700          }
33701
33702          break;
33703
33704        case '/':
33705          switch (s) {
33706            case S_TAG:
33707              el.setTagName(source.slice(start, p));
33708
33709            case S_ATTR_END:
33710            case S_TAG_SPACE:
33711            case S_TAG_CLOSE:
33712              s = S_TAG_CLOSE;
33713              el.closed = true;
33714
33715            case S_ATTR_NOQUOT_VALUE:
33716            case S_ATTR:
33717            case S_ATTR_SPACE:
33718              break;
33719            //case S_EQ:
33720
33721            default:
33722              throw new Error("attribute invalid close char('/')");
33723            // No known test case
33724          }
33725
33726          break;
33727
33728        case '':
33729          //end document
33730          errorHandler.error('unexpected end of input');
33731
33732          if (s == S_TAG) {
33733            el.setTagName(source.slice(start, p));
33734          }
33735
33736          return p;
33737
33738        case '>':
33739          switch (s) {
33740            case S_TAG:
33741              el.setTagName(source.slice(start, p));
33742
33743            case S_ATTR_END:
33744            case S_TAG_SPACE:
33745            case S_TAG_CLOSE:
33746              break;
33747            //normal
33748
33749            case S_ATTR_NOQUOT_VALUE: //Compatible state
33750
33751            case S_ATTR:
33752              value = source.slice(start, p);
33753
33754              if (value.slice(-1) === '/') {
33755                el.closed = true;
33756                value = value.slice(0, -1);
33757              }
33758
33759            case S_ATTR_SPACE:
33760              if (s === S_ATTR_SPACE) {
33761                value = attrName;
33762              }
33763
33764              if (s == S_ATTR_NOQUOT_VALUE) {
33765                errorHandler.warning('attribute "' + value + '" missed quot(")!');
33766                addAttribute(attrName, value.replace(/&#?\w+;/g, entityReplacer), start);
33767              } else {
33768                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)) {
33769                  errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
33770                }
33771
33772                addAttribute(value, value, start);
33773              }
33774
33775              break;
33776
33777            case S_EQ:
33778              throw new Error('attribute value missed!!');
33779          } //			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
33780
33781
33782          return p;
33783
33784        /*xml space '\x20' | #x9 | #xD | #xA; */
33785
33786        case "\x80":
33787          c = ' ';
33788
33789        default:
33790          if (c <= ' ') {
33791            //space
33792            switch (s) {
33793              case S_TAG:
33794                el.setTagName(source.slice(start, p)); //tagName
33795
33796                s = S_TAG_SPACE;
33797                break;
33798
33799              case S_ATTR:
33800                attrName = source.slice(start, p);
33801                s = S_ATTR_SPACE;
33802                break;
33803
33804              case S_ATTR_NOQUOT_VALUE:
33805                var value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer);
33806                errorHandler.warning('attribute "' + value + '" missed quot(")!!');
33807                addAttribute(attrName, value, start);
33808
33809              case S_ATTR_END:
33810                s = S_TAG_SPACE;
33811                break;
33812              //case S_TAG_SPACE:
33813              //case S_EQ:
33814              //case S_ATTR_SPACE:
33815              //	void();break;
33816              //case S_TAG_CLOSE:
33817              //ignore warning
33818            }
33819          } else {
33820            //not space
33821            //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
33822            //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
33823            switch (s) {
33824              //case S_TAG:void();break;
33825              //case S_ATTR:void();break;
33826              //case S_ATTR_NOQUOT_VALUE:void();break;
33827              case S_ATTR_SPACE:
33828                el.tagName;
33829
33830                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
33831                  errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
33832                }
33833
33834                addAttribute(attrName, attrName, start);
33835                start = p;
33836                s = S_ATTR;
33837                break;
33838
33839              case S_ATTR_END:
33840                errorHandler.warning('attribute space is required"' + attrName + '"!!');
33841
33842              case S_TAG_SPACE:
33843                s = S_ATTR;
33844                start = p;
33845                break;
33846
33847              case S_EQ:
33848                s = S_ATTR_NOQUOT_VALUE;
33849                start = p;
33850                break;
33851
33852              case S_TAG_CLOSE:
33853                throw new Error("elements closed character '/' and '>' must be connected to");
33854            }
33855          }
33856
33857      } //end outer switch
33858      //console.log('p++',p)
33859
33860
33861      p++;
33862    }
33863  }
33864  /**
33865   * @return true if has new namespace define
33866   */
33867
33868
33869  function appendElement(el, domBuilder, currentNSMap) {
33870    var tagName = el.tagName;
33871    var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
33872
33873    var i = el.length;
33874
33875    while (i--) {
33876      var a = el[i];
33877      var qName = a.qName;
33878      var value = a.value;
33879      var nsp = qName.indexOf(':');
33880
33881      if (nsp > 0) {
33882        var prefix = a.prefix = qName.slice(0, nsp);
33883        var localName = qName.slice(nsp + 1);
33884        var nsPrefix = prefix === 'xmlns' && localName;
33885      } else {
33886        localName = qName;
33887        prefix = null;
33888        nsPrefix = qName === 'xmlns' && '';
33889      } //can not set prefix,because prefix !== ''
33890
33891
33892      a.localName = localName; //prefix == null for no ns prefix attribute 
33893
33894      if (nsPrefix !== false) {
33895        //hack!!
33896        if (localNSMap == null) {
33897          localNSMap = {}; //console.log(currentNSMap,0)
33898
33899          _copy(currentNSMap, currentNSMap = {}); //console.log(currentNSMap,1)
33900
33901        }
33902
33903        currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
33904        a.uri = NAMESPACE$1.XMLNS;
33905        domBuilder.startPrefixMapping(nsPrefix, value);
33906      }
33907    }
33908
33909    var i = el.length;
33910
33911    while (i--) {
33912      a = el[i];
33913      var prefix = a.prefix;
33914
33915      if (prefix) {
33916        //no prefix attribute has no namespace
33917        if (prefix === 'xml') {
33918          a.uri = NAMESPACE$1.XML;
33919        }
33920
33921        if (prefix !== 'xmlns') {
33922          a.uri = currentNSMap[prefix || '']; //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
33923        }
33924      }
33925    }
33926
33927    var nsp = tagName.indexOf(':');
33928
33929    if (nsp > 0) {
33930      prefix = el.prefix = tagName.slice(0, nsp);
33931      localName = el.localName = tagName.slice(nsp + 1);
33932    } else {
33933      prefix = null; //important!!
33934
33935      localName = el.localName = tagName;
33936    } //no prefix element has default namespace
33937
33938
33939    var ns = el.uri = currentNSMap[prefix || ''];
33940    domBuilder.startElement(ns, localName, tagName, el); //endPrefixMapping and startPrefixMapping have not any help for dom builder
33941    //localNSMap = null
33942
33943    if (el.closed) {
33944      domBuilder.endElement(ns, localName, tagName);
33945
33946      if (localNSMap) {
33947        for (prefix in localNSMap) {
33948          domBuilder.endPrefixMapping(prefix);
33949        }
33950      }
33951    } else {
33952      el.currentNSMap = currentNSMap;
33953      el.localNSMap = localNSMap; //parseStack.push(el);
33954
33955      return true;
33956    }
33957  }
33958
33959  function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
33960    if (/^(?:script|textarea)$/i.test(tagName)) {
33961      var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd);
33962      var text = source.substring(elStartEnd + 1, elEndStart);
33963
33964      if (/[&<]/.test(text)) {
33965        if (/^script$/i.test(tagName)) {
33966          //if(!/\]\]>/.test(text)){
33967          //lexHandler.startCDATA();
33968          domBuilder.characters(text, 0, text.length); //lexHandler.endCDATA();
33969
33970          return elEndStart; //}
33971        } //}else{//text area
33972
33973
33974        text = text.replace(/&#?\w+;/g, entityReplacer);
33975        domBuilder.characters(text, 0, text.length);
33976        return elEndStart; //}
33977      }
33978    }
33979
33980    return elStartEnd + 1;
33981  }
33982
33983  function fixSelfClosed(source, elStartEnd, tagName, closeMap) {
33984    //if(tagName in closeMap){
33985    var pos = closeMap[tagName];
33986
33987    if (pos == null) {
33988      //console.log(tagName)
33989      pos = source.lastIndexOf('</' + tagName + '>');
33990
33991      if (pos < elStartEnd) {
33992        //忘记闭合
33993        pos = source.lastIndexOf('</' + tagName);
33994      }
33995
33996      closeMap[tagName] = pos;
33997    }
33998
33999    return pos < elStartEnd; //} 
34000  }
34001
34002  function _copy(source, target) {
34003    for (var n in source) {
34004      target[n] = source[n];
34005    }
34006  }
34007
34008  function parseDCC(source, start, domBuilder, errorHandler) {
34009    //sure start with '<!'
34010    var next = source.charAt(start + 2);
34011
34012    switch (next) {
34013      case '-':
34014        if (source.charAt(start + 3) === '-') {
34015          var end = source.indexOf('-->', start + 4); //append comment source.substring(4,end)//<!--
34016
34017          if (end > start) {
34018            domBuilder.comment(source, start + 4, end - start - 4);
34019            return end + 3;
34020          } else {
34021            errorHandler.error("Unclosed comment");
34022            return -1;
34023          }
34024        } else {
34025          //error
34026          return -1;
34027        }
34028
34029      default:
34030        if (source.substr(start + 3, 6) == 'CDATA[') {
34031          var end = source.indexOf(']]>', start + 9);
34032          domBuilder.startCDATA();
34033          domBuilder.characters(source, start + 9, end - start - 9);
34034          domBuilder.endCDATA();
34035          return end + 3;
34036        } //<!DOCTYPE
34037        //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) 
34038
34039
34040        var matchs = split(source, start);
34041        var len = matchs.length;
34042
34043        if (len > 1 && /!doctype/i.test(matchs[0][0])) {
34044          var name = matchs[1][0];
34045          var pubid = false;
34046          var sysid = false;
34047
34048          if (len > 3) {
34049            if (/^public$/i.test(matchs[2][0])) {
34050              pubid = matchs[3][0];
34051              sysid = len > 4 && matchs[4][0];
34052            } else if (/^system$/i.test(matchs[2][0])) {
34053              sysid = matchs[3][0];
34054            }
34055          }
34056
34057          var lastMatch = matchs[len - 1];
34058          domBuilder.startDTD(name, pubid, sysid);
34059          domBuilder.endDTD();
34060          return lastMatch.index + lastMatch[0].length;
34061        }
34062
34063    }
34064
34065    return -1;
34066  }
34067
34068  function parseInstruction(source, start, domBuilder) {
34069    var end = source.indexOf('?>', start);
34070
34071    if (end) {
34072      var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
34073
34074      if (match) {
34075        match[0].length;
34076        domBuilder.processingInstruction(match[1], match[2]);
34077        return end + 2;
34078      } else {
34079        //error
34080        return -1;
34081      }
34082    }
34083
34084    return -1;
34085  }
34086
34087  function ElementAttributes() {
34088    this.attributeNames = {};
34089  }
34090
34091  ElementAttributes.prototype = {
34092    setTagName: function setTagName(tagName) {
34093      if (!tagNamePattern.test(tagName)) {
34094        throw new Error('invalid tagName:' + tagName);
34095      }
34096
34097      this.tagName = tagName;
34098    },
34099    addValue: function addValue(qName, value, offset) {
34100      if (!tagNamePattern.test(qName)) {
34101        throw new Error('invalid attribute:' + qName);
34102      }
34103
34104      this.attributeNames[qName] = this.length;
34105      this[this.length++] = {
34106        qName: qName,
34107        value: value,
34108        offset: offset
34109      };
34110    },
34111    length: 0,
34112    getLocalName: function getLocalName(i) {
34113      return this[i].localName;
34114    },
34115    getLocator: function getLocator(i) {
34116      return this[i].locator;
34117    },
34118    getQName: function getQName(i) {
34119      return this[i].qName;
34120    },
34121    getURI: function getURI(i) {
34122      return this[i].uri;
34123    },
34124    getValue: function getValue(i) {
34125      return this[i].value;
34126    } //	,getIndex:function(uri, localName)){
34127    //		if(localName){
34128    //			
34129    //		}else{
34130    //			var qName = uri
34131    //		}
34132    //	},
34133    //	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
34134    //	getType:function(uri,localName){}
34135    //	getType:function(i){},
34136
34137  };
34138
34139  function split(source, start) {
34140    var match;
34141    var buf = [];
34142    var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
34143    reg.lastIndex = start;
34144    reg.exec(source); //skip <
34145
34146    while (match = reg.exec(source)) {
34147      buf.push(match);
34148      if (match[1]) return buf;
34149    }
34150  }
34151
34152  var XMLReader_1 = XMLReader;
34153  var ParseError_1 = ParseError;
34154  var sax = {
34155    XMLReader: XMLReader_1,
34156    ParseError: ParseError_1
34157  };
34158
34159  var NAMESPACE = conventions.NAMESPACE;
34160  /**
34161   * A prerequisite for `[].filter`, to drop elements that are empty
34162   * @param {string} input
34163   * @returns {boolean}
34164   */
34165
34166  function notEmptyString(input) {
34167    return input !== '';
34168  }
34169  /**
34170   * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace
34171   * @see https://infra.spec.whatwg.org/#ascii-whitespace
34172   *
34173   * @param {string} input
34174   * @returns {string[]} (can be empty)
34175   */
34176
34177
34178  function splitOnASCIIWhitespace(input) {
34179    // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE
34180    return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [];
34181  }
34182  /**
34183   * Adds element as a key to current if it is not already present.
34184   *
34185   * @param {Record<string, boolean | undefined>} current
34186   * @param {string} element
34187   * @returns {Record<string, boolean | undefined>}
34188   */
34189
34190
34191  function orderedSetReducer(current, element) {
34192    if (!current.hasOwnProperty(element)) {
34193      current[element] = true;
34194    }
34195
34196    return current;
34197  }
34198  /**
34199   * @see https://infra.spec.whatwg.org/#ordered-set
34200   * @param {string} input
34201   * @returns {string[]}
34202   */
34203
34204
34205  function toOrderedSet(input) {
34206    if (!input) return [];
34207    var list = splitOnASCIIWhitespace(input);
34208    return Object.keys(list.reduce(orderedSetReducer, {}));
34209  }
34210  /**
34211   * Uses `list.indexOf` to implement something like `Array.prototype.includes`,
34212   * which we can not rely on being available.
34213   *
34214   * @param {any[]} list
34215   * @returns {function(any): boolean}
34216   */
34217
34218
34219  function arrayIncludes(list) {
34220    return function (element) {
34221      return list && list.indexOf(element) !== -1;
34222    };
34223  }
34224
34225  function copy(src, dest) {
34226    for (var p in src) {
34227      dest[p] = src[p];
34228    }
34229  }
34230  /**
34231  ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
34232  ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
34233   */
34234
34235
34236  function _extends(Class, Super) {
34237    var pt = Class.prototype;
34238
34239    if (!(pt instanceof Super)) {
34240      var t = function t() {};
34241      t.prototype = Super.prototype;
34242      t = new t();
34243      copy(pt, t);
34244      Class.prototype = pt = t;
34245    }
34246
34247    if (pt.constructor != Class) {
34248      if (typeof Class != 'function') {
34249        console.error("unknow Class:" + Class);
34250      }
34251
34252      pt.constructor = Class;
34253    }
34254  } // Node Types
34255
34256
34257  var NodeType = {};
34258  var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1;
34259  var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2;
34260  var TEXT_NODE = NodeType.TEXT_NODE = 3;
34261  var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4;
34262  var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5;
34263  var ENTITY_NODE = NodeType.ENTITY_NODE = 6;
34264  var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
34265  var COMMENT_NODE = NodeType.COMMENT_NODE = 8;
34266  var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9;
34267  var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10;
34268  var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11;
34269  var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode
34270
34271  var ExceptionCode = {};
34272  var ExceptionMessage = {};
34273  ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1);
34274  ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2);
34275  var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3);
34276  ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4);
34277  ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5);
34278  ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6);
34279  ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7);
34280  var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8);
34281  ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9);
34282  var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10); //level2
34283
34284  ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11);
34285  ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12);
34286  ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13);
34287  ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14);
34288  ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15);
34289  /**
34290   * DOM Level 2
34291   * Object DOMException
34292   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
34293   * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
34294   */
34295
34296  function DOMException(code, message) {
34297    if (message instanceof Error) {
34298      var error = message;
34299    } else {
34300      error = this;
34301      Error.call(this, ExceptionMessage[code]);
34302      this.message = ExceptionMessage[code];
34303      if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
34304    }
34305
34306    error.code = code;
34307    if (message) this.message = this.message + ": " + message;
34308    return error;
34309  }
34310  DOMException.prototype = Error.prototype;
34311  copy(ExceptionCode, DOMException);
34312  /**
34313   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
34314   * 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.
34315   * The items in the NodeList are accessible via an integral index, starting from 0.
34316   */
34317
34318  function NodeList() {}
34319  NodeList.prototype = {
34320    /**
34321     * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
34322     * @standard level1
34323     */
34324    length: 0,
34325
34326    /**
34327     * 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.
34328     * @standard level1
34329     * @param index  unsigned long 
34330     *   Index into the collection.
34331     * @return Node
34332     * 	The node at the indexth position in the NodeList, or null if that is not a valid index. 
34333     */
34334    item: function item(index) {
34335      return this[index] || null;
34336    },
34337    toString: function toString(isHTML, nodeFilter) {
34338      for (var buf = [], i = 0; i < this.length; i++) {
34339        serializeToString(this[i], buf, isHTML, nodeFilter);
34340      }
34341
34342      return buf.join('');
34343    }
34344  };
34345
34346  function LiveNodeList(node, refresh) {
34347    this._node = node;
34348    this._refresh = refresh;
34349
34350    _updateLiveList(this);
34351  }
34352
34353  function _updateLiveList(list) {
34354    var inc = list._node._inc || list._node.ownerDocument._inc;
34355
34356    if (list._inc != inc) {
34357      var ls = list._refresh(list._node); //console.log(ls.length)
34358
34359
34360      __set__(list, 'length', ls.length);
34361
34362      copy(ls, list);
34363      list._inc = inc;
34364    }
34365  }
34366
34367  LiveNodeList.prototype.item = function (i) {
34368    _updateLiveList(this);
34369
34370    return this[i];
34371  };
34372
34373  _extends(LiveNodeList, NodeList);
34374  /**
34375   * Objects implementing the NamedNodeMap interface are used
34376   * to represent collections of nodes that can be accessed by name.
34377   * Note that NamedNodeMap does not inherit from NodeList;
34378   * NamedNodeMaps are not maintained in any particular order.
34379   * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
34380   * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
34381   * and does not imply that the DOM specifies an order to these Nodes.
34382   * NamedNodeMap objects in the DOM are live.
34383   * used for attributes or DocumentType entities 
34384   */
34385
34386
34387  function NamedNodeMap() {}
34388
34389  function _findNodeIndex(list, node) {
34390    var i = list.length;
34391
34392    while (i--) {
34393      if (list[i] === node) {
34394        return i;
34395      }
34396    }
34397  }
34398
34399  function _addNamedNode(el, list, newAttr, oldAttr) {
34400    if (oldAttr) {
34401      list[_findNodeIndex(list, oldAttr)] = newAttr;
34402    } else {
34403      list[list.length++] = newAttr;
34404    }
34405
34406    if (el) {
34407      newAttr.ownerElement = el;
34408      var doc = el.ownerDocument;
34409
34410      if (doc) {
34411        oldAttr && _onRemoveAttribute(doc, el, oldAttr);
34412
34413        _onAddAttribute(doc, el, newAttr);
34414      }
34415    }
34416  }
34417
34418  function _removeNamedNode(el, list, attr) {
34419    //console.log('remove attr:'+attr)
34420    var i = _findNodeIndex(list, attr);
34421
34422    if (i >= 0) {
34423      var lastIndex = list.length - 1;
34424
34425      while (i < lastIndex) {
34426        list[i] = list[++i];
34427      }
34428
34429      list.length = lastIndex;
34430
34431      if (el) {
34432        var doc = el.ownerDocument;
34433
34434        if (doc) {
34435          _onRemoveAttribute(doc, el, attr);
34436
34437          attr.ownerElement = null;
34438        }
34439      }
34440    } else {
34441      throw DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
34442    }
34443  }
34444
34445  NamedNodeMap.prototype = {
34446    length: 0,
34447    item: NodeList.prototype.item,
34448    getNamedItem: function getNamedItem(key) {
34449      //		if(key.indexOf(':')>0 || key == 'xmlns'){
34450      //			return null;
34451      //		}
34452      //console.log()
34453      var i = this.length;
34454
34455      while (i--) {
34456        var attr = this[i]; //console.log(attr.nodeName,key)
34457
34458        if (attr.nodeName == key) {
34459          return attr;
34460        }
34461      }
34462    },
34463    setNamedItem: function setNamedItem(attr) {
34464      var el = attr.ownerElement;
34465
34466      if (el && el != this._ownerElement) {
34467        throw new DOMException(INUSE_ATTRIBUTE_ERR);
34468      }
34469
34470      var oldAttr = this.getNamedItem(attr.nodeName);
34471
34472      _addNamedNode(this._ownerElement, this, attr, oldAttr);
34473
34474      return oldAttr;
34475    },
34476
34477    /* returns Node */
34478    setNamedItemNS: function setNamedItemNS(attr) {
vendor: 4,998 bytes, lines 34479-34606
34479      // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
34480      var el = attr.ownerElement,
34481          oldAttr;
34482
34483      if (el && el != this._ownerElement) {
34484        throw new DOMException(INUSE_ATTRIBUTE_ERR);
34485      }
34486
34487      oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
34488
34489      _addNamedNode(this._ownerElement, this, attr, oldAttr);
34490
34491      return oldAttr;
34492    },
34493
34494    /* returns Node */
34495    removeNamedItem: function removeNamedItem(key) {
34496      var attr = this.getNamedItem(key);
34497
34498      _removeNamedNode(this._ownerElement, this, attr);
34499
34500      return attr;
34501    },
34502    // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
34503    //for level2
34504    removeNamedItemNS: function removeNamedItemNS(namespaceURI, localName) {
34505      var attr = this.getNamedItemNS(namespaceURI, localName);
34506
34507      _removeNamedNode(this._ownerElement, this, attr);
34508
34509      return attr;
34510    },
34511    getNamedItemNS: function getNamedItemNS(namespaceURI, localName) {
34512      var i = this.length;
34513
34514      while (i--) {
34515        var node = this[i];
34516
34517        if (node.localName == localName && node.namespaceURI == namespaceURI) {
34518          return node;
34519        }
34520      }
34521
34522      return null;
34523    }
34524  };
34525  /**
34526   * The DOMImplementation interface represents an object providing methods
34527   * which are not dependent on any particular document.
34528   * Such an object is returned by the `Document.implementation` property.
34529   *
34530   * __The individual methods describe the differences compared to the specs.__
34531   *
34532   * @constructor
34533   *
34534   * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
34535   * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
34536   * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
34537   * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
34538   * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
34539   */
34540
34541  function DOMImplementation() {}
34542
34543  DOMImplementation.prototype = {
34544    /**
34545     * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
34546     * The different implementations fairly diverged in what kind of features were reported.
34547     * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
34548     *
34549     * @deprecated It is deprecated and modern browsers return true in all cases.
34550     *
34551     * @param {string} feature
34552     * @param {string} [version]
34553     * @returns {boolean} always true
34554     *
34555     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
34556     * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
34557     * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
34558     */
34559    hasFeature: function hasFeature(feature, version) {
34560      return true;
34561    },
34562
34563    /**
34564     * Creates an XML Document object of the specified type with its document element.
34565     *
34566     * __It behaves slightly different from the description in the living standard__:
34567     * - There is no interface/class `XMLDocument`, it returns a `Document` instance.
34568     * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
34569     * - this implementation is not validating names or qualified names
34570     *   (when parsing XML strings, the SAX parser takes care of that)
34571     *
34572     * @param {string|null} namespaceURI
34573     * @param {string} qualifiedName
34574     * @param {DocumentType=null} doctype
34575     * @returns {Document}
34576     *
34577     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
34578     * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
34579     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument  DOM Level 2 Core
34580     *
34581     * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
34582     * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
34583     * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
34584     */
34585    createDocument: function createDocument(namespaceURI, qualifiedName, doctype) {
34586      var doc = new Document();
34587      doc.implementation = this;
34588      doc.childNodes = new NodeList();
34589      doc.doctype = doctype || null;
34590
34591      if (doctype) {
34592        doc.appendChild(doctype);
34593      }
34594
34595      if (qualifiedName) {
34596        var root = doc.createElementNS(namespaceURI, qualifiedName);
34597        doc.appendChild(root);
34598      }
34599
34600      return doc;
34601    },
34602
34603    /**
34604     * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
34605     *
34606     * __This behavior is slightly different from the in the specs__:
34607     * - this implementation is not validating names or qualified names
34608     *   (when parsing XML strings, the SAX parser takes care of that)
34609     *
34610     * @param {string} qualifiedName
34611     * @param {string} [publicId]
34612     * @param {string} [systemId]
34613     * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
34614     * 				  or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
34615     *
34616     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
34617     * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
34618     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
34619     *
34620     * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
34621     * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
34622     * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
34623     */
34624    createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) {
34625      var node = new DocumentType();
34626      node.name = qualifiedName;
34627      node.nodeName = qualifiedName;
34628      node.publicId = publicId || '';
34629      node.systemId = systemId || '';
34630      return node;
34631    }
34632  };
34633  /**
34634   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
34635   */
34636
34637  function Node() {}
34638  Node.prototype = {
34639    firstChild: null,
34640    lastChild: null,
34641    previousSibling: null,
34642    nextSibling: null,
34643    attributes: null,
34644    parentNode: null,
34645    childNodes: null,
34646    ownerDocument: null,
34647    nodeValue: null,
34648    namespaceURI: null,
34649    prefix: null,
34650    localName: null,
34651    // Modified in DOM Level 2:
34652    insertBefore: function insertBefore(newChild, refChild) {
34653      //raises 
34654      return _insertBefore(this, newChild, refChild);
34655    },
34656    replaceChild: function replaceChild(newChild, oldChild) {
34657      //raises 
34658      this.insertBefore(newChild, oldChild);
34659
34660      if (oldChild) {
34661        this.removeChild(oldChild);
34662      }
34663    },
34664    removeChild: function removeChild(oldChild) {
34665      return _removeChild(this, oldChild);
34666    },
34667    appendChild: function appendChild(newChild) {
34668      return this.insertBefore(newChild, null);
34669    },
34670    hasChildNodes: function hasChildNodes() {
34671      return this.firstChild != null;
34672    },
34673    cloneNode: function cloneNode(deep) {
34674      return _cloneNode(this.ownerDocument || this, this, deep);
34675    },
34676    // Modified in DOM Level 2:
34677    normalize: function normalize() {
34678      var child = this.firstChild;
34679
34680      while (child) {
34681        var next = child.nextSibling;
34682
34683        if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) {
34684          this.removeChild(next);
34685          child.appendData(next.data);
34686        } else {
34687          child.normalize();
34688          child = next;
34689        }
34690      }
34691    },
34692    // Introduced in DOM Level 2:
34693    isSupported: function isSupported(feature, version) {
34694      return this.ownerDocument.implementation.hasFeature(feature, version);
34695    },
34696    // Introduced in DOM Level 2:
34697    hasAttributes: function hasAttributes() {
34698      return this.attributes.length > 0;
34699    },
34700    lookupPrefix: function lookupPrefix(namespaceURI) {
34701      var el = this;
34702
34703      while (el) {
34704        var map = el._nsMap; //console.dir(map)
34705
34706        if (map) {
34707          for (var n in map) {
34708            if (map[n] == namespaceURI) {
34709              return n;
34710            }
34711          }
34712        }
34713
34714        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
34715      }
34716
34717      return null;
34718    },
34719    // Introduced in DOM Level 3:
34720    lookupNamespaceURI: function lookupNamespaceURI(prefix) {
34721      var el = this;
34722
34723      while (el) {
34724        var map = el._nsMap; //console.dir(map)
34725
34726        if (map) {
34727          if (prefix in map) {
34728            return map[prefix];
34729          }
34730        }
34731
34732        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
34733      }
34734
34735      return null;
34736    },
34737    // Introduced in DOM Level 3:
34738    isDefaultNamespace: function isDefaultNamespace(namespaceURI) {
34739      var prefix = this.lookupPrefix(namespaceURI);
34740      return prefix == null;
34741    }
34742  };
34743
34744  function _xmlEncoder(c) {
34745    return c == '<' && '&lt;' || c == '>' && '&gt;' || c == '&' && '&amp;' || c == '"' && '&quot;
34745' || '&#' + c.charCodeAt() + ';';
34746  }
34747
34748  copy(NodeType, Node);
34749  copy(NodeType, Node.prototype);
34750  /**
34751   * @param callback return true for continue,false for break
34752   * @return boolean true: break visit;
34753   */
34754
34755  function _visitNode(node, callback) {
34756    if (callback(node)) {
34757      return true;
34758    }
34759
34760    if (node = node.firstChild) {
34761      do {
34762        if (_visitNode(node, callback)) {
34763          return true;
34764        }
34765      } while (node = node.nextSibling);
34766    }
34767  }
34768
34769  function Document() {}
34770
34771  function _onAddAttribute(doc, el, newAttr) {
34772    doc && doc._inc++;
34773    var ns = newAttr.namespaceURI;
34774
34775    if (ns === NAMESPACE.XMLNS) {
34776      //update namespace
34777      el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value;
34778    }
34779  }
34780
34781  function _onRemoveAttribute(doc, el, newAttr, remove) {
34782    doc && doc._inc++;
34783    var ns = newAttr.namespaceURI;
34784
34785    if (ns === NAMESPACE.XMLNS) {
34786      //update namespace
34787      delete el._nsMap[newAttr.prefix ? newAttr.localName : ''];
34788    }
34789  }
34790
34791  function _onUpdateChild(doc, el, newChild) {
34792    if (doc && doc._inc) {
34793      doc._inc++; //update childNodes
34794
34795      var cs = el.childNodes;
34796
34797      if (newChild) {
34798        cs[cs.length++] = newChild;
34799      } else {
34800        //console.log(1)
34801        var child = el.firstChild;
34802        var i = 0;
34803
34804        while (child) {
34805          cs[i++] = child;
34806          child = child.nextSibling;
34807        }
34808
34809        cs.length = i;
34810      }
34811    }
34812  }
34813  /**
34814   * attributes;
34815   * children;
34816   * 
34817   * writeable properties:
34818   * nodeValue,Attr:value,CharacterData:data
34819   * prefix
34820   */
34821
34822
34823  function _removeChild(parentNode, child) {
34824    var previous = child.previousSibling;
34825    var next = child.nextSibling;
34826
34827    if (previous) {
34828      previous.nextSibling = next;
34829    } else {
34830      parentNode.firstChild = next;
34831    }
34832
34833    if (next) {
34834      next.previousSibling = previous;
34835    } else {
34836      parentNode.lastChild = previous;
34837    }
34838
34839    _onUpdateChild(parentNode.ownerDocument, parentNode);
34840
34841    return child;
34842  }
34843  /**
34844   * preformance key(refChild == null)
34845   */
34846
34847
34848  function _insertBefore(parentNode, newChild, nextChild) {
34849    var cp = newChild.parentNode;
34850
34851    if (cp) {
34852      cp.removeChild(newChild); //remove and update
34853    }
34854
34855    if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
34856      var newFirst = newChild.firstChild;
34857
34858      if (newFirst == null) {
34859        return newChild;
34860      }
34861
34862      var newLast = newChild.lastChild;
34863    } else {
34864      newFirst = newLast = newChild;
34865    }
34866
34867    var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild;
34868    newFirst.previousSibling = pre;
34869    newLast.nextSibling = nextChild;
34870
34871    if (pre) {
34872      pre.nextSibling = newFirst;
34873    } else {
34874      parentNode.firstChild = newFirst;
34875    }
34876
34877    if (nextChild == null) {
34878      parentNode.lastChild = newLast;
34879    } else {
34880      nextChild.previousSibling = newLast;
34881    }
34882
34883    do {
34884      newFirst.parentNode = parentNode;
34885    } while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
34886
34887    _onUpdateChild(parentNode.ownerDocument || parentNode, parentNode); //console.log(parentNode.lastChild.nextSibling == null)
34888
34889
34890    if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
34891      newChild.firstChild = newChild.lastChild = null;
34892    }
34893
34894    return newChild;
34895  }
34896
34897  function _appendSingleChild(parentNode, newChild) {
34898    var cp = newChild.parentNode;
34899
34900    if (cp) {
34901      var pre = parentNode.lastChild;
34902      cp.removeChild(newChild); //remove and update
34903
34904      var pre = parentNode.lastChild;
34905    }
34906
34907    var pre = parentNode.lastChild;
34908    newChild.parentNode = parentNode;
34909    newChild.previousSibling = pre;
34910    newChild.nextSibling = null;
34911
34912    if (pre) {
34913      pre.nextSibling = newChild;
34914    } else {
34915      parentNode.firstChild = newChild;
34916    }
34917
34918    parentNode.lastChild = newChild;
34919
34920    _onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
34921
34922    return newChild; //console.log("__aa",parentNode.lastChild.nextSibling == null)
34923  }
34924
34925  Document.prototype = {
34926    //implementation : null,
34927    nodeName: '#document',
34928    nodeType: DOCUMENT_NODE,
34929    doctype: null,
34930    documentElement: null,
34931    _inc: 1,
34932    insertBefore: function insertBefore(newChild, refChild) {
34933      //raises
34934      if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
34935        var child = newChild.firstChild;
34936
34937        while (child) {
34938          var next = child.nextSibling;
34939          this.insertBefore(child, refChild);
34940          child = next;
34941        }
34942
34943        return newChild;
34944      }
34945
34946      if (this.documentElement == null && newChild.nodeType == ELEMENT_NODE) {
34947        this.documentElement = newChild;
34948      }
34949
34950      return _insertBefore(this, newChild, refChild), newChild.ownerDocument = this, newChild;
34951    },
34952    removeChild: function removeChild(oldChild) {
34953      if (this.documentElement == oldChild) {
34954        this.documentElement = null;
34955      }
34956
34957      return _removeChild(this, oldChild);
34958    },
34959    // Introduced in DOM Level 2:
34960    importNode: function importNode(importedNode, deep) {
34961      return _importNode(this, importedNode, deep);
34962    },
34963    // Introduced in DOM Level 2:
34964    getElementById: function getElementById(id) {
34965      var rtv = null;
34966
34967      _visitNode(this.documentElement, function (node) {
34968        if (node.nodeType == ELEMENT_NODE) {
34969          if (node.getAttribute('id') == id) {
34970            rtv = node;
34971            return true;
34972          }
34973        }
34974      });
34975
34976      return rtv;
34977    },
34978
34979    /**
34980     * The `getElementsByClassName` method of `Document` interface returns an array-like object
34981     * of all child elements which have **all** of the given class 
34981name(s).
34982     *
34983     * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters.
34984     *
34985     *
34986     * Warning: This is a live LiveNodeList.
34987     * Changes in the DOM will reflect in the array as the changes occur.
34988     * If an element selected by this array no longer qualifies for the selector,
34989     * it will automatically be removed. Be aware of this for iteration purposes.
34990     *
34991     * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace
34992     *
34993     * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
34994     * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
34995     */
34996    getElementsByClassName: function getElementsByClassName(classNames) {
34997      var classNamesSet = toOrderedSet(classNames);
34998      return new LiveNodeList(this, function (base) {
34999        var ls = [];
35000
35001        if (classNamesSet.length > 0) {
35002          _visitNode(base.documentElement, function (node) {
35003            if (node !== base && node.nodeType === ELEMENT_NODE) {
35004              var nodeClassNames = node.getAttribute('class'); // can be null if the attribute does not exist
35005
35006              if (nodeClassNames) {
35007                // before splitting and iterating just compare them for the most common case
35008                var matches = classNames === nodeClassNames;
35009
35010                if (!matches) {
35011                  var nodeClassNamesSet = toOrderedSet(nodeClassNames);
35012                  matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet));
35013                }
35014
35015                if (matches) {
35016                  ls.push(node);
35017                }
35018              }
35019            }
35020          });
35021        }
35022
35023        return ls;
35024      });
35025    },
35026    //document factory method:
35027    createElement: function createElement(tagName) {
35028      var node = new Element();
35029      node.ownerDocument = this;
35030      node.nodeName = tagName;
35031      node.tagName = tagName;
35032      node.localName = tagName;
35033      node.childNodes = new NodeList();
35034      var attrs = node.attributes = new NamedNodeMap();
35035      attrs._ownerElement = node;
35036      return node;
35037    },
35038    createDocumentFragment: function createDocumentFragment() {
35039      var node = new DocumentFragment();
35040      node.ownerDocument = this;
35041      node.childNodes = new NodeList();
35042      return node;
35043    },
35044    createTextNode: function createTextNode(data) {
35045      var node = new Text();
35046      node.ownerDocument = this;
35047      node.appendData(data);
35048      return node;
35049    },
35050    createComment: function createComment(data) {
35051      var node = new Comment();
35052      node.ownerDocument = this;
35053      node.appendData(data);
35054      return node;
35055    },
35056    createCDATASection: function createCDATASection(data) {
35057      var node = new CDATASection();
35058      node.ownerDocument = this;
35059      node.appendData(data);
35060      return node;
35061    },
35062    createProcessingInstruction: function createProcessingInstruction(target, data) {
35063      var node = new ProcessingInstruction();
35064      node.ownerDocument = this;
35065      node.tagName = node.target = target;
35066      node.nodeValue = node.data = data;
35067      return node;
35068    },
35069    createAttribute: function createAttribute(name) {
35070      var node = new Attr();
35071      node.ownerDocument = this;
35072      node.name = name;
35073      node.nodeName = name;
35074      node.localName = name;
35075      node.specified = true;
35076      return node;
35077    },
35078    createEntityReference: function createEntityReference(name) {
35079      var node = new EntityReference();
35080      node.ownerDocument = this;
35081      node.nodeName = name;
35082      return node;
35083    },
35084    // Introduced in DOM Level 2:
35085    createElementNS: function createElementNS(namespaceURI, qualifiedName) {
35086      var node = new Element();
35087      var pl = qualifiedName.split(':');
35088      var attrs = node.attributes = new NamedNodeMap();
35089      node.childNodes = new NodeList();
35090      node.ownerDocument = this;
35091      node.nodeName = qualifiedName;
35092      node.tagName = qualifiedName;
35093      node.namespaceURI = namespaceURI;
35094
35095      if (pl.length == 2) {
35096        node.prefix = pl[0];
35097        node.localName = pl[1];
35098      } else {
35099        //el.prefix = null;
35100        node.localName = qualifiedName;
35101      }
35102
35103      attrs._ownerElement = node;
35104      return node;
35105    },
35106    // Introduced in DOM Level 2:
35107    createAttributeNS: function createAttributeNS(namespaceURI, qualifiedName) {
35108      var node = new Attr();
35109      var pl = qualifiedName.split(':');
35110      node.ownerDocument = this;
35111      node.nodeName = qualifiedName;
35112      node.name = qualifiedName;
35113      node.namespaceURI = namespaceURI;
35114      node.specified = true;
35115
35116      if (pl.length == 2) {
35117        node.prefix = pl[0];
35118        node.localName = pl[1];
35119      } else {
35120        //el.prefix = null;
35121        node.localName = qualifiedName;
35122      }
35123
35124      return node;
35125    }
35126  };
35127
35128  _extends(Document, Node);
35129
35130  function Element() {
35131    this._nsMap = {};
35132  }
35133  Element.prototype = {
35134    nodeType: ELEMENT_NODE,
35135    hasAttribute: function hasAttribute(name) {
35136      return this.getAttributeNode(name) != null;
35137    },
35138    getAttribute: function getAttribute(name) {
35139      var attr = this.getAttributeNode(name);
35140      return attr && attr.value || '';
35141    },
35142    getAttributeNode: function getAttributeNode(name) {
35143      return this.attributes.getNamedItem(name);
35144    },
35145    setAttribute: function setAttribute(name, value) {
35146      var attr = this.ownerDocument.createAttribute(name);
35147      attr.value = attr.nodeValue = "" + value;
35148      this.setAttributeNode(attr);
35149    },
35150    removeAttribute: function removeAttribute(name) {
35151      var attr = this.getAttributeNode(name);
35152      attr && this.removeAttributeNode(attr);
35153    },
35154    //four real opeartion method
35155    appendChild: function appendChild(newChild) {
35156      if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
35157        return this.insertBefore(newChild, null);
35158      } else {
35159        return _appendSingleChild(this, newChild);
35160      }
35161    },
35162    setAttributeNode: function setAttributeNode(newAttr) {
35163      return this.attributes.setNamedItem(newAttr);
35164    },
35165    setAttributeNodeNS: function setAttributeNodeNS(newAttr) {
35166      return this.attributes.setNamedItemNS(newAttr);
35167    },
35168    removeAttributeNode: function removeAttributeNode(oldAttr) {
35169      //console.log(this == oldAttr.ownerElement)
35170      return this.attributes.removeNamedItem(oldAttr.nodeName);
35171    },
35172    //get real attribute name,and remove it by removeAttributeNode
35173    removeAttributeNS: function removeAttributeNS(namespaceURI, localName) {
35174      var old = this.getAttributeNodeNS(namespaceURI, localName);
35175      old && this.removeAttributeNode(old);
35176    },
35177    hasAttributeNS: function hasAttributeNS(namespaceURI, localName) {
35178      return this.getAttributeNodeNS(namespaceURI, localName) != null;
35179    },
35180    getAttributeNS: function getAttributeNS(namespaceURI, localName) {
35181      var attr = this.getAttributeNodeNS(namespaceURI, localName);
35182      return attr && attr.value || '';
35183    },
35184    setAttributeNS: function setAttributeNS(namespaceURI, qualifiedName, value) {
35185      var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
35186      attr.value = attr.nodeValue = "" + value;
35187      this.setAttributeNode(attr);
35188    },
35189    getAttributeNodeNS: function getAttributeNodeNS(namespaceURI, localName) {
35190      return this.attributes.getNamedItemNS(namespaceURI, localName);
35191    },
35192    getElementsByTagName: function getElementsByTagName(tagName) {
35193      return new LiveNodeList(this, function (base) {
35194        var ls = [];
35195
35196        _visitNode(base, function (node) {
35197          if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) {
35198            ls.push(node);
35199          }
35200        });
35201
35202        return ls;
35203      });
35204    },
35205    getElementsByTagNameNS: function getElementsByTagNameNS(namespaceURI, localName) {
35206      return new LiveNodeList(this, function (base) {
35207        var ls = [];
35208
35209        _visitNode(base, function (node) {
35210          if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) {
35211            ls.push(node);
35212          }
35213        });
35214
35215        return ls;
35216      });
35217    }
35218  };
35219  Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
35220  Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
35221
35222  _extends(Element, Node);
35223
35224  function Attr() {}
35225  Attr.prototype.nodeType = ATTRIBUTE_NODE;
35226
35227  _extends(Attr, Node);
35228
35229  function CharacterData() {}
35230  CharacterData.prototype = {
35231    data: '',
35232    substringData: function substringData(offset, count) {
35233      return this.data.substring(offset, offset + count);
35234    },
35235    appendData: function appendData(text) {
35236      text = this.data + text;
35237      this.nodeValue = this.data = text;
35238      this.length = text.length;
35239    },
35240    insertData: function insertData(offset, text) {
35241      this.replaceData(offset, 0, text);
35242    },
35243    appendChild: function appendChild(newChild) {
35244      throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]);
35245    },
35246    deleteData: function deleteData(offset, count) {
35247      this.replaceData(offset, count, "");
35248    },
35249    replaceData: function replaceData(offset, count, text) {
35250      var start = this.data.substring(0, offset);
35251      var end = this.data.substring(offset + count);
vendor: 9,228 bytes, lines 35252-35552
35252      text = start + text + end;
35253      this.nodeValue = this.data = text;
35254      this.length = text.length;
35255    }
35256  };
35257
35258  _extends(CharacterData, Node);
35259
35260  function Text() {}
35261  Text.prototype = {
35262    nodeName: "#text",
35263    nodeType: TEXT_NODE,
35264    splitText: function splitText(offset) {
35265      var text = this.data;
35266      var newText = text.substring(offset);
35267      text = text.substring(0, offset);
35268      this.data = this.nodeValue = text;
35269      this.length = text.length;
35270      var newNode = this.ownerDocument.createTextNode(newText);
35271
35272      if (this.parentNode) {
35273        this.parentNode.insertBefore(newNode, this.nextSibling);
35274      }
35275
35276      return newNode;
35277    }
35278  };
35279
35280  _extends(Text, CharacterData);
35281
35282  function Comment() {}
35283  Comment.prototype = {
35284    nodeName: "#comment",
35285    nodeType: COMMENT_NODE
35286  };
35287
35288  _extends(Comment, CharacterData);
35289
35290  function CDATASection() {}
35291  CDATASection.prototype = {
35292    nodeName: "#cdata-section",
35293    nodeType: CDATA_SECTION_NODE
35294  };
35295
35296  _extends(CDATASection, CharacterData);
35297
35298  function DocumentType() {}
35299  DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
35300
35301  _extends(DocumentType, Node);
35302
35303  function Notation() {}
35304  Notation.prototype.nodeType = NOTATION_NODE;
35305
35306  _extends(Notation, Node);
35307
35308  function Entity() {}
35309  Entity.prototype.nodeType = ENTITY_NODE;
35310
35311  _extends(Entity, Node);
35312
35313  function EntityReference() {}
35314  EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
35315
35316  _extends(EntityReference, Node);
35317
35318  function DocumentFragment() {}
35319  DocumentFragment.prototype.nodeName = "#document-fragment";
35320  DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
35321
35322  _extends(DocumentFragment, Node);
35323
35324  function ProcessingInstruction() {}
35325
35326  ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
35327
35328  _extends(ProcessingInstruction, Node);
35329
35330  function XMLSerializer() {}
35331
35332  XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) {
35333    return nodeSerializeToString.call(node, isHtml, nodeFilter);
35334  };
35335
35336  Node.prototype.toString = nodeSerializeToString;
35337
35338  function nodeSerializeToString(isHtml, nodeFilter) {
35339    var buf = [];
35340    var refNode = this.nodeType == 9 && this.documentElement || this;
35341    var prefix = refNode.prefix;
35342    var uri = refNode.namespaceURI;
35343
35344    if (uri && prefix == null) {
35345      //console.log(prefix)
35346      var prefix = refNode.lookupPrefix(uri);
35347
35348      if (prefix == null) {
35349        //isHTML = true;
35350        var visibleNamespaces = [{
35351          namespace: uri,
35352          prefix: null
35353        } //{namespace:uri,prefix:''}
35354        ];
35355      }
35356    }
35357
35358    serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join(''))
35359
35360    return buf.join('');
35361  }
35362
35363  function needNamespaceDefine(node, isHTML, visibleNamespaces) {
35364    var prefix = node.prefix || '';
35365    var uri = node.namespaceURI; // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) ,
35366    // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl :
35367    // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty.
35368    // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using)
35369    // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared :
35370    // > [...] Furthermore, the attribute value [...] must not be an empty string.
35371    // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document.
35372
35373    if (!uri) {
35374      return false;
35375    }
35376
35377    if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) {
35378      return false;
35379    }
35380
35381    var i = visibleNamespaces.length;
35382
35383    while (i--) {
35384      var ns = visibleNamespaces[i]; // get namespace prefix
35385
35386      if (ns.prefix === prefix) {
35387        return ns.namespace !== uri;
35388      }
35389    }
35390
35391    return true;
35392  }
35393  /**
35394   * Well-formed constraint: No < in Attribute Values
35395   * The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <.
35396   * @see https://www.w3.org/TR/xml/#CleanAttrVals
35397   * @see https://www.w3.org/TR/xml/#NT-AttValue
35398   */
35399
35400
35401  function addSerializedAttribute(buf, qualifiedName, value) {
35402    buf.push(' ', qualifiedName, '="', value.replace(/[<&"]/g, _xmlEncoder), '"');
35403  }
35404
35405  function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) {
35406    if (!visibleNamespaces) {
35407      visibleNamespaces = [];
35408    }
35409
35410    if (nodeFilter) {
35411      node = nodeFilter(node);
35412
35413      if (node) {
35414        if (typeof node == 'string') {
35415          buf.push(node);
35416          return;
35417        }
35418      } else {
35419        return;
35420      } //buf.sort.apply(attrs, attributeSorter);
35421
35422    }
35423
35424    switch (node.nodeType) {
35425      case ELEMENT_NODE:
35426        var attrs = node.attributes;
35427        var len = attrs.length;
35428        var child = node.firstChild;
35429        var nodeName = node.tagName;
35430        isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML;
35431        var prefixedNodeName = nodeName;
35432
35433        if (!isHTML && !node.prefix && node.namespaceURI) {
35434          var defaultNS;
35435
35436          for (var ai = 0; ai < attrs.length; ai++) {
35437            if (attrs.item(ai).name === 'xmlns') {
35438              defaultNS = attrs.item(ai).value;
35439              break;
35440            }
35441          }
35442
35443          if (defaultNS !== node.namespaceURI) {
35444            for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
35445              var namespace = visibleNamespaces[nsi];
35446
35447              if (namespace.namespace === node.namespaceURI) {
35448                if (namespace.prefix) {
35449                  prefixedNodeName = namespace.prefix + ':' + nodeName;
35450                }
35451
35452                break;
35453              }
35454            }
35455          }
35456        }
35457
35458        buf.push('<', prefixedNodeName);
35459
35460        for (var i = 0; i < len; i++) {
35461          // add namespaces for attributes
35462          var attr = attrs.item(i);
35463
35464          if (attr.prefix == 'xmlns') {
35465            visibleNamespaces.push({
35466              prefix: attr.localName,
35467              namespace: attr.value
35468            });
35469          } else if (attr.nodeName == 'xmlns') {
35470            visibleNamespaces.push({
35471              prefix: '',
35472              namespace: attr.value
35473            });
35474          }
35475        }
35476
35477        for (var i = 0; i < len; i++) {
35478          var attr = attrs.item(i);
35479
35480          if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) {
35481            var prefix = attr.prefix || '';
35482            var uri = attr.namespaceURI;
35483            addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
35484            visibleNamespaces.push({
35485              prefix: prefix,
35486              namespace: uri
35487            });
35488          }
35489
35490          serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces);
35491        } // add namespace for current node		
35492
35493
35494        if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
35495          var prefix = node.prefix || '';
35496          var uri = node.namespaceURI;
35497          addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
35498          visibleNamespaces.push({
35499            prefix: prefix,
35500            namespace: uri
35501          });
35502        }
35503
35504        if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
35505          buf.push('>'); //if is cdata child node
35506
35507          if (isHTML && /^script$/i.test(nodeName)) {
35508            while (child) {
35509              if (child.data) {
35510                buf.push(child.data);
35511              } else {
35512                serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
35513              }
35514
35515              child = child.nextSibling;
35516            }
35517          } else {
35518            while (child) {
35519              serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
35520              child = child.nextSibling;
35521            }
35522          }
35523
35524          buf.push('</', prefixedNodeName, '>');
35525        } else {
35526          buf.push('/>');
35527        } // remove added visible namespaces
35528        //visibleNamespaces.length = startVisibleNamespaces;
35529
35530
35531        return;
35532
35533      case DOCUMENT_NODE:
35534      case DOCUMENT_FRAGMENT_NODE:
35535        var child = node.firstChild;
35536
35537        while (child) {
35538          serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
35539          child = child.nextSibling;
35540        }
35541
35542        return;
35543
35544      case ATTRIBUTE_NODE:
35545        return addSerializedAttribute(buf, node.name, node.value);
35546
35547      case TEXT_NODE:
35548        /**
35549         * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
35550         * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
35551         * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
35552         * `&amp;` and `&lt;` respectively.
35553         * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
35554         * be escaped using either `&gt;` or a character reference when it appears in the string `]]>` in content,
35555         * when that string is not marking the end of a CDATA section.
35556         *
35557         * In the content of elements, character data is any string of characters
35558         * which does not contain the start-delimiter of any markup
35559         * and does not include the CDATA-section-close delimiter, `]]>`.
35560         *
35561         * @see https://www.w3.org/TR/xml/#NT-CharData
35562         */
35563        return buf.push(node.data.replace(/[<&]/g, _xmlEncoder).replace(/]]>/g, ']]&gt;'));
35564
35565      case CDATA_SECTION_NODE:
35566        return buf.push('<![CDATA[', node.data, ']]>');
35567
35568      case COMMENT_NODE:
35569        return buf.push("<!--", node.data, "-->");
35570
35571      case DOCUMENT_TYPE_NODE:
35572        var pubid = node.publicId;
35573        var sysid = node.systemId;
35574        buf.push('<!DOCTYPE ', node.name);
35575
35576        if (pubid) {
35577          buf.push(' PUBLIC ', pubid);
35578
35579          if (sysid && sysid != '.') {
35580            buf.push(' ', sysid);
35581          }
35582
35583          buf.push('>');
35584        } else if (sysid && sysid != '.') {
35585          buf.push(' SYSTEM ', sysid, '>');
35586        } else {
35587          var sub = node.internalSubset;
35588
35589          if (sub) {
35590            buf.push(" [", sub, "]");
35591          }
35592
35593          buf.push(">");
35594        }
35595
35596        return;
35597
35598      case PROCESSING_INSTRUCTION_NODE:
35599        return buf.push("<?", node.target, " ", node.data, "?>");
35600
35601      case ENTITY_REFERENCE_NODE:
35602        return buf.push('&', node.nodeName, ';');
35603      //case ENTITY_NODE:
35604      //case NOTATION_NODE:
35605
35606      default:
35607        buf.push('??', node.nodeName);
35608    }
35609  }
35610
35611  function _importNode(doc, node, deep) {
35612    var node2;
35613
35614    switch (node.nodeType) {
35615      case ELEMENT_NODE:
35616        node2 = node.cloneNode(false);
35617        node2.ownerDocument = doc;
35618      //var attrs = node2.attributes;
35619      //var len = attrs.length;
35620      //for(var i=0;i<len;i++){
35621      //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
35622      //}
35623
35624      case DOCUMENT_FRAGMENT_NODE:
35625        break;
35626
35627      case ATTRIBUTE_NODE:
35628        deep = true;
35629        break;
35630      //case ENTITY_REFERENCE_NODE:
35631      //case PROCESSING_INSTRUCTION_NODE:
35632      ////case TEXT_NODE:
35633      //case CDATA_SECTION_NODE:
35634      //case COMMENT_NODE:
35635      //	deep = false;
35636      //	break;
35637      //case DOCUMENT_NODE:
35638      //case DOCUMENT_TYPE_NODE:
35639      //cannot be imported.
35640      //case ENTITY_NODE:
35641      //case NOTATION_NODE:
35642      //can not hit in level3
35643      //default:throw e;
35644    }
35645
35646    if (!node2) {
35647      node2 = node.cloneNode(false); //false
35648    }
35649
35650    node2.ownerDocument = doc;
35651    node2.parentNode = null;
35652
35653    if (deep) {
35654      var child = node.firstChild;
35655
35656      while (child) {
35657        node2.appendChild(_importNode(doc, child, deep));
35658        child = child.nextSibling;
35659      }
35660    }
35661
35662    return node2;
35663  } //
35664  //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
35665  //					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
35666
35667
35668  function _cloneNode(doc, node, deep) {
35669    var node2 = new node.constructor();
35670
35671    for (var n in node) {
35672      var v = node[n];
35673
35674      if (typeof v != 'object') {
35675        if (v != node2[n]) {
35676          node2[n] = v;
35677        }
35678      }
35679    }
35680
35681    if (node.childNodes) {
35682      node2.childNodes = new NodeList();
35683    }
35684
35685    node2.ownerDocument = doc;
35686
35687    switch (node2.nodeType) {
35688      case ELEMENT_NODE:
35689        var attrs = node.attributes;
35690        var attrs2 = node2.attributes = new NamedNodeMap();
35691        var len = attrs.length;
35692        attrs2._ownerElement = node2;
35693
35694        for (var i = 0; i < len; i++) {
35695          node2.setAttributeNode(_cloneNode(doc, attrs.item(i), true));
35696        }
35697
35698        break;
35699
35700      case ATTRIBUTE_NODE:
35701        deep = true;
35702    }
35703
35704    if (deep) {
35705      var child = node.firstChild;
35706
35707      while (child) {
35708        node2.appendChild(_cloneNode(doc, child, deep));
35709        child = child.nextSibling;
35710      }
35711    }
35712
35713    return node2;
35714  }
35715
35716  function __set__(object, key, value) {
35717    object[key] = value;
35718  } //do dynamic
35719
35720
35721  try {
35722    if (Object.defineProperty) {
35723      var getTextContent = function getTextContent(node) {
35724        switch (node.nodeType) {
35725          case ELEMENT_NODE:
35726          case DOCUMENT_FRAGMENT_NODE:
35727            var buf = [];
35728            node = node.firstChild;
35729
35730            while (node) {
35731              if (node.nodeType !== 7 && node.nodeType !== 8) {
35732                buf.push(getTextContent(node));
35733              }
35734
35735              node = node.nextSibling;
35736            }
35737
35738            return buf.join('');
35739
35740          default:
35741            return node.nodeValue;
35742        }
35743      };
35744
35745      Object.defineProperty(LiveNodeList.prototype, 'length', {
35746        get: function get() {
35747          _updateLiveList(this);
35748
35749          return this.$$length;
35750        }
35751      });
35752      Object.defineProperty(Node.prototype, 'textContent', {
35753        get: function get() {
35754          return getTextContent(this);
35755        },
35756        set: function set(data) {
35757          switch (this.nodeType) {
35758            case ELEMENT_NODE:
35759            case DOCUMENT_FRAGMENT_NODE:
35760              while (this.firstChild) {
35761                this.removeChild(this.firstChild);
35762              }
35763
35764              if (data || String(data)) {
35765                this.appendChild(this.ownerDocument.createTextNode(data));
35766              }
35767
35768              break;
35769
35770            default:
35771              this.data = data;
35772              this.value = data;
35773              this.nodeValue = data;
35774          }
35775        }
35776      });
35777
35778      __set__ = function __set__(object, key, value) {
35779        //console.log(value)
35780        object['$$' + key] = value;
35781      };
35782    }
35783  } catch (e) {//ie8
35784  } //if(typeof require == 'function'){
35785
35786
35787  var DocumentType_1 = DocumentType;
35788  var DOMException_1 = DOMException;
35789  var DOMImplementation_1 = DOMImplementation;
35790  var Element_1 = Element;
35791  var Node_1 = Node;
35792  var NodeList_1 = NodeList;
35793  var XMLSerializer_1 = XMLSerializer; //}
35794
35795  var dom = {
35796    DocumentType: DocumentType_1,
35797    DOMException: DOMException_1,
35798    DOMImplementation: DOMImplementation_1,
35799    Element: Element_1,
35800    Node: Node_1,
35801    NodeList: NodeList_1,
35802    XMLSerializer: XMLSerializer_1
35803  };
35804
35805  var domParser = createCommonjsModule(function (module, exports) {
35806    var NAMESPACE = conventions.NAMESPACE;
35807
35808    function DOMParser(options) {
35809      this.options = options || {
35810        locator: {}
35811      };
35812    }
35813
35814    DOMParser.prototype.parseFromString = function (source, mimeType) {
35815      var options = this.options;
35816      var sax = new XMLReader();
35817      var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler
35818
35819      var errorHandler = options.errorHandler;
35820      var locator = options.locator;
35821      var defaultNSMap = options.xmlns || {};
35822      var isHTML = /\/x?html?$/.test(mimeType); //mimeType.toLowerCase().indexOf('html') > -1;
35823
35824      var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
35825
35826      if (locator) {
35827        domBuilder.setDocumentLocator(locator);
35828      }
35829
35830      sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator);
35831      sax.domBuilder = options.domBuilder || domBuilder;
35832
35833      if (isHTML) {
35834        defaultNSMap[''] = NAMESPACE.HTML;
35835      }
35836
35837      defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
35838
35839      if (source && typeof source === 'string') {
35840        sax.parse(source, defaultNSMap, entityMap);
35841      } else {
35842        sax.errorHandler.error("invalid doc source");
35843      }
35844
35845      return domBuilder.doc;
35846    };
35847
35848    function buildErrorHandler(errorImpl, domBuilder, locator) {
35849      if (!errorImpl) {
35850        if (domBuilder instanceof DOMHandler) {
35851          return domBuilder;
35852        }
35853
35854        errorImpl = domBuilder;
35855      }
35856
35857      var errorHandler = {};
35858      var isCallback = errorImpl instanceof Function;
35859      locator = locator || {};
35860
35861      function build(key) {
35862        var fn = errorImpl[key];
35863
35864        if (!fn && isCallback) {
35865          fn = errorImpl.length == 2 ? function (msg) {
35866            errorImpl(key, msg);
35867          } : errorImpl;
35868        }
35869
35870        errorHandler[key] = fn && function (msg) {
35871          fn('[xmldom ' + key + ']\t' + msg + _locator(locator));
35872        } || function () {};
35873      }
35874
35875      build('warning');
35876      build('error');
35877      build('fatalError');
35878      return errorHandler;
35879    } //console.log('#\n\n\n\n\n\n\n####')
35880
35881    /**
35882     * +ContentHandler+ErrorHandler
35883     * +LexicalHandler+EntityResolver2
35884     * -DeclHandler-DTDHandler
35885     *
35886     * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
35887     * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
35888     * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
35889     */
35890
35891
35892    function DOMHandler() {
35893      this.cdata = false;
35894    }
35895
35896    function position(locator, node) {
35897      node.lineNumber = locator.lineNumber;
35898      node.columnNumber = locator.columnNumber;
35899    }
35900    /**
35901     * @see org.xml.sax.ContentHandler#startDocument
35902     * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
35903     */
35904
35905
35906    DOMHandler.prototype = {
35907      startDocument: function startDocument() {
35908        this.doc = new DOMImplementation().createDocument(null, null, null);
35909
35910        if (this.locator) {
35911          this.doc.documentURI = this.locator.systemId;
35912        }
35913      },
35914      startElement: function startElement(namespaceURI, localName, qName, attrs) {
35915        var doc = this.doc;
35916        var el = doc.createElementNS(namespaceURI, qName || localName);
35917        var len = attrs.length;
35918        appendElement(this, el);
35919        this.currentElement = el;
35920        this.locator && position(this.locator, el);
35921
35922        for (var i = 0; i < len; i++) {
35923          var namespaceURI = attrs.getURI(i);
35924          var value = attrs.getValue(i);
35925          var qName = attrs.getQName(i);
35926          var attr = doc.createAttributeNS(namespaceURI, qName);
35927          this.locator && position(attrs.getLocator(i), attr);
35928          attr.value = attr.nodeValue = value;
35929          el.setAttributeNode(attr);
35930        }
35931      },
35932      endElement: function endElement(namespaceURI, localName, qName) {
35933        var current = this.currentElement;
35934        current.tagName;
35935        this.currentElement = current.parentNode;
35936      },
35937      startPrefixMapping: function startPrefixMapping(prefix, uri) {},
35938      endPrefixMapping: function endPrefixMapping(prefix) {},
35939      processingInstruction: function processingInstruction(target, data) {
35940        var ins = this.doc.createProcessingInstruction(target, data);
35941        this.locator && position(this.locator, ins);
35942        appendElement(this, ins);
35943      },
35944      ignorableWhitespace: function ignorableWhitespace(ch, start, length) {},
35945      characters: function characters(chars, start, length) {
35946        chars = _toString.apply(this, arguments); //console.log(chars)
35947
35948        if (chars) {
35949          if (this.cdata) {
35950            var charNode = this.doc.createCDATASection(chars);
35951          } else {
35952            var charNode = this.doc.createTextNode(chars);
35953          }
35954
35955          if (this.currentElement) {
35956            this.currentElement.appendChild(charNode);
35957          } else if (/^\s*$/.test(chars)) {
35958            this.doc.appendChild(charNode); //process xml
35959          }
35960
35961          this.locator && position(this.locator, charNode);
35962        }
35963      },
35964      skippedEntity: function skippedEntity(name) {},
35965      endDocument: function endDocument() {
35966        this.doc.normalize();
35967      },
35968      setDocumentLocator: function setDocumentLocator(locator) {
35969        if (this.locator = locator) {
35970          // && !('lineNumber' in locator)){
35971          locator.lineNumber = 0;
35972        }
35973      },
35974      //LexicalHandler
35975      comment: function comment(chars, start, length) {
35976        chars = _toString.apply(this, arguments);
35977        var comm = this.doc.createComment(chars);
35978        this.locator && position(this.locator, comm);
35979        appendElement(this, comm);
35980      },
35981      startCDATA: function startCDATA() {
35982        //used in characters() methods
35983        this.cdata = true;
35984      },
35985      endCDATA: function endCDATA() {
35986        this.cdata = false;
35987      },
35988      startDTD: function startDTD(name, publicId, systemId) {
35989        var impl = this.doc.implementation;
35990
35991        if (impl && impl.createDocumentType) {
35992          var dt = impl.createDocumentType(name, publicId, systemId);
35993          this.locator && position(this.locator, dt);
35994          appendElement(this, dt);
35995        }
35996      },
35997
35998      /**
35999       * @see org.xml.sax.ErrorHandler
36000       * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
36001       */
36002      warning: function warning(error) {
36003        console.warn('[xmldom warning]\t' + error, _locator(this.locator));
36004      },
36005      error: function error(_error) {
36006        console.error('[xmldom error]\t' + _error, _locator(this.locator));
36007      },
36008      fatalError: function fatalError(error) {
36009        throw new ParseError(error, this.locator);
36010      }
36011    };
36012
36013    function _locator(l) {
36014      if (l) {
36015        return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']';
36016      }
36017    }
36018
36019    function _toString(chars, start, length) {
36020      if (typeof chars == 'string') {
36021        return chars.substr(start, length);
36022      } else {
36023        //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
36024        if (chars.length >= start + length || start) {
36025          return new java.lang.String(chars, start, length) + '';
36026        }
36027
36028        return chars;
36029      }
36030    }
36031    /*
36032     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
36033     * used method of org.xml.sax.ext.LexicalHandler:
36034     *  #comment(chars, start, length)
36035     *  #startCDATA()
36036     *  #endCDATA()
36037     *  #startDTD(name, publicId, systemId)
36038     *
36039     *
36040     * IGNORED method of org.xml.sax.ext.LexicalHandler:
36041     *  #endDTD()
36042     *  #startEntity(name)
36043     *  #endEntity(name)
36044     *
36045     *
36046     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
36047     * IGNORED method of org.xml.sax.ext.DeclHandler
36048     * 	#attributeDecl(eName, aName, type, mode, value)
36049     *  #elementDecl(name, model)
36050     *  #externalEntityDecl(name, publicId, systemId)
36051     *  #internalEntityDecl(name, value)
36052     * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
36053     * IGNORED method of org.xml.sax.EntityResolver2
36054     *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
36055     *  #resolveEntity(publicId, systemId)
36056     *  #getExternalSubset(name, baseURI)
36057     * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
36058     * IGNORED method of org.xml.sax.DTDHandler
36059     *  #notationDecl(name, publicId, systemId) {};
36060     *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
36061     */
36062
36063
36064    "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) {
36065      DOMHandler.prototype[key] = function () {
36066        return null;
36067      };
36068    });
36069    /* 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 */
36070
36071    function appendElement(hander, node) {
36072      if (!hander.currentElement) {
36073        hander.doc.appendChild(node);
36074      } else {
36075        hander.currentElement.appendChild(node);
36076      }
36077    } //appendChild and setAttributeNS are preformance key
36078    //if(typeof require == 'function'){
36079
36080
36081    var XMLReader = sax.XMLReader;
36082    var ParseError = sax.ParseError;
36083    var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation;
36084    exports.XMLSerializer = dom.XMLSerializer;
36085    exports.DOMParser = DOMParser;
36086    exports.__DOMHandler = DOMHandler; //}
36087  });
36088  domParser.DOMImplementation;
36089  domParser.XMLSerializer;
36090  var domParser_3 = domParser.DOMParser;
36091  domParser.__DOMHandler;
36092
36093  /*! @name mpd-parser @version 0.19.0 @license Apache-2.0 */
36094
36095  var isObject = function isObject(obj) {
36096    return !!obj && typeof obj === 'object';
36097  };
36098
36099  var merge = function merge() {
36100    for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
36101      objects[_key] = arguments[_key];
36102    }
36103
36104    return objects.reduce(function (result, source) {
36105      if (typeof source !== 'object') {
36106        return result;
36107      }
36108
36109      Object.keys(source).forEach(function (key) {
36110        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
36111          result[key] = result[key].concat(source[key]);
36112        } else if (isObject(result[key]) && isObject(source[key])) {
36113          result[key] = merge(result[key], source[key]);
36114        } else {
36115          result[key] = source[key];
36116        }
36117      });
36118      return result;
36119    }, {});
36120  };
36121
36122  var values = function values(o) {
36123    return Object.keys(o).map(function (k) {
36124      return o[k];
36125    });
36126  };
36127
36128  var range = function range(start, end) {
36129    var result = [];
36130
36131    for (var i = start; i < end; i++) {
36132      result.push(i);
36133    }
36134
36135    return result;
36136  };
36137
36138  var flatten = function flatten(lists) {
36139    return lists.reduce(function (x, y) {
36140      return x.concat(y);
36141    }, []);
36142  };
36143
36144  var from = function from(list) {
36145    if (!list.length) {
36146      return [];
36147    }
36148
36149    var result = [];
36150
36151    for (var i = 0; i < list.length; i++) {
36152      result.push(list[i]);
36153    }
36154
36155    return result;
36156  };
36157
36158  var findIndexes = function findIndexes(l, key) {
36159    return l.reduce(function (a, e, i) {
36160      if (e[key]) {
36161        a.push(i);
36162      }
36163
36164      return a;
36165    }, []);
36166  };
36167
36168  var errors = {
36169    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
36170    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
36171    DASH_INVALID_XML: 'DASH_INVALID_XML',
36172    NO_BASE_URL: 'NO_BASE_URL',
36173    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
36174    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
36175    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
36176  };
36177  /**
36178   * @typedef {Object} SingleUri
36179   * @property {string} uri - relative location of segment
36180   * @property {string} resolvedUri - resolved location of segment
36181   * @property {Object} byterange - Object containing information on how to make byte range
36182   *   requests following byte-range-spec per RFC2616.
36183   * @property {String} byterange.length - length of range request
36184   * @property {String} byterange.offset - byte offset of range request
36185   *
36186   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
36187   */
36188
36189  /**
36190   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
36191   * that conforms to how m3u8-parser is structured
36192   *
36193   * @see https://github.com/videojs/m3u8-parser
36194   *
36195   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
36196   * @param {string} source - source url for segment
36197   * @param {string} range - optional range used for range calls,
36198   *   follows  RFC 2616, Clause 14.35.1
36199   * @return {SingleUri} full segment information transformed into a format similar
36200   *   to m3u8-parser
36201   */
36202
36203  var urlTypeToSegment = function urlTypeToSegment(_ref) {
36204    var _ref$baseUrl = _ref.baseUrl,
36205        baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
36206        _ref$source = _ref.source,
36207        source = _ref$source === void 0 ? '' : _ref$source,
36208        _ref$range = _ref.range,
36209        range = _ref$range === void 0 ? '' : _ref$range,
36210        _ref$indexRange = _ref.indexRange,
36211        indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange;
36212    var segment = {
36213      uri: source,
36214      resolvedUri: resolveUrl$1(baseUrl || '', source)
36215    };
36216
36217    if (range || indexRange) {
36218      var rangeStr = range ? range : indexRange;
36219      var ranges = rangeStr.split('-');
36220      var startRange = parseInt(ranges[0], 10);
36221      var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
36222      // RFC 2616, Clause 14.35.1
36223
36224      segment.byterange = {
36225        length: endRange - startRange + 1,
36226        offset: startRange
36227      };
36228    }
36229
36230    return segment;
36231  };
36232
36233  var byteRangeToString = function byteRangeToString(byterange) {
36234    // `endRange` is one less than `offset + length` because the HTTP range
36235    // header uses inclusive ranges
36236    var endRange = byterange.offset + byterange.length - 1;
36237    return byterange.offset + "-" + endRange;
36238  };
36239  /**
36240   * parse the end number attribue that can be a string
36241   * number, or undefined.
36242   *
36243   * @param {string|number|undefined} endNumber
36244   *        The end number attribute.
36245   *
36246   * @return {number|null}
36247   *          The result of parsing the end number.
36248   */
36249
36250
36251  var parseEndNumber = function parseEndNumber(endNumber) {
36252    if (endNumber && typeof endNumber !== 'number') {
36253      endNumber = parseInt(endNumber, 10);
36254    }
36255
36256    if (isNaN(endNumber)) {
36257      return null;
36258    }
36259
36260    return endNumber;
36261  };
36262  /**
36263   * Functions for calculating the range of available segments in static and dynamic
36264   * manifests.
36265   */
36266
36267
36268  var segmentRange = {
36269    /**
36270     * Returns the entire range of available segments for a static MPD
36271     *
36272     * @param {Object} attributes
36273     *        Inheritied MPD attributes
36274     * @return {{ start: number, end: number }}
36275     *         The start and end numbers for available segments
36276     */
36277    "static": function _static(attributes) {
36278      var duration = attributes.duration,
36279          _attributes$timescale = attributes.timescale,
36280          timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
36281          sourceDuration = attributes.sourceDuration,
36282          periodDuration = attributes.periodDuration;
36283      var endNumber = parseEndNumber(attributes.endNumber);
36284      var segmentDuration = duration / timescale;
36285
36286      if (typeof endNumber === 'number') {
36287        return {
36288          start: 0,
36289          end: endNumber
36290        };
36291      }
36292
36293      if (typeof periodDuration === 'number') {
36294        return {
36295          start: 0,
36296          end: periodDuration / segmentDuration
36297        };
36298      }
36299
36300      return {
36301        start: 0,
36302        end: sourceDuration / segmentDuration
36303      };
36304    },
36305
36306    /**
36307     * Returns the current live window range of available segments for a dynamic MPD
36308     *
36309     * @param {Object} attributes
36310     *        Inheritied MPD attributes
36311     * @return {{ start: number, end: number }}
36312     *         The start and end numbers for available segments
36313     */
36314    dynamic: function dynamic(attributes) {
36315      var NOW = attributes.NOW,
36316          clientOffset = attributes.clientOffset,
36317          availabilityStartTime = attributes.availabilityStartTime,
36318          _attributes$timescale2 = attributes.timescale,
36319          timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
36320          duration = attributes.duration,
36321          _attributes$start = attributes.start,
36322          start = _attributes$start === void 0 ? 0 : _attributes$start,
36323          _attributes$minimumUp = attributes.minimumUpdatePeriod,
36324          minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
36325          _attributes$timeShift = attributes.timeShiftBufferDepth,
36326          timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
36327      var endNumber = parseEndNumber(attributes.endNumber);
36328      var now = (NOW + clientOffset) / 1000;
36329      var periodStartWC = availabilityStartTime + start;
36330      var periodEndWC = now + minimumUpdatePeriod;
36331      var periodDuration = periodEndWC - periodStartWC;
36332      var segmentCount = Math.ceil(periodDuration * timescale / duration);
36333      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
36334      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
36335      return {
36336        start: Math.max(0, availableStart),
36337        end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
36338      };
36339    }
36340  };
36341  /**
36342   * Maps a range of numbers to objects with information needed to build the corresponding
36343   * segment list
36344   *
36345   * @name toSegmentsCallback
36346   * @function
36347   * @param {number} number
36348   *        Number of the segment
36349   * @param {number} index
36350   *        Index of the number in the range list
36351   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
36352   *         Object with segment timing and duration info
36353   */
36354
36355  /**
36356   * Returns a callback for Array.prototype.map for mapping a range of numbers to
36357   * information needed to build the segment list.
36358   *
36359   * @param {Object} attributes
36360   *        Inherited MPD attributes
36361   * @return {toSegmentsCallback}
36362   *         Callback map function
36363   */
36364
36365  var toSegments = function toSegments(attributes) {
36366    return function (number, index) {
36367      var duration = attributes.duration,
36368          _attributes$timescale3 = attributes.timescale,
36369          timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
36370          periodIndex = attributes.periodIndex,
36371          _attributes$startNumb = attributes.startNumber,
36372          startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
36373      return {
36374        number: startNumber + number,
36375        duration: duration / timescale,
36376        timeline: periodIndex,
36377        time: index * duration
36378      };
36379    };
36380  };
36381  /**
36382   * Returns a list of objects containing segment timing and duration info used for
36383   * building the list of segments. This uses the @duration attribute specified
36384   * in the MPD manifest to derive the range of segments.
36385   *
36386   * @param {Object} attributes
36387   *        Inherited MPD attributes
36388   * @return {{number: number, duration: number, time: number, timeline: number}[]}
36389   *         List of Objects with segment timing and duration info
36390   */
36391
36392
36393  var parseByDuration = function parseByDuration(attributes) {
36394    var type = attributes.type,
36395        duration = attributes.duration,
36396        _attributes$timescale4 = attributes.timescale,
36397        timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
36398        periodDuration = attributes.periodDuration,
36399        sourceDuration = attributes.sourceDuration;
36400
36401    var _segmentRange$type = segmentRange[type](attributes),
36402        start = _segmentRange$type.start,
36403        end = _segmentRange$type.end;
36404
36405    var segments = range(start, end).map(toSegments(attributes));
36406
36407    if (type === 'static') {
36408      var index = segments.length - 1; // section is either a period or the full source
36409
36410      var sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
36411
36412      segments[index].duration = sectionDuration - duration / timescale * index;
36413    }
36414
36415    return segments;
36416  };
36417  /**
36418   * Translates SegmentBase into a set of segments.
36419   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
36420   * node should be translated into segment.
36421   *
36422   * @param {Object} attributes
36423   *   Object containing all inherited attributes from parent elements with attribute
36424   *   names as keys
36425   * @return {Object.<Array>} list of segments
36426   */
36427
36428
36429  var segmentsFromBase = function segmentsFromBase(attributes) {
36430    var baseUrl = attributes.baseUrl,
36431        _attributes$initializ = attributes.initialization,
36432        initialization = _attributes$initializ === void 0 ? {}
36432 : _attributes$initializ,
36433        sourceDuration = attributes.sourceDuration,
36434        _attributes$indexRang = attributes.indexRange,
36435        indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
36436        duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
36437
36438    if (!baseUrl) {
36439      throw new Error(errors.NO_BASE_URL);
36440    }
36441
36442    var initSegment = urlTypeToSegment({
36443      baseUrl: baseUrl,
36444      source: initialization.sourceURL,
36445      range: initialization.range
36446    });
36447    var segment = urlTypeToSegment({
36448      baseUrl: baseUrl,
36449      source: baseUrl,
36450      indexRange: indexRange
36451    });
36452    segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
36453    // (since SegmentBase is only for one total segment)
36454
36455    if (duration) {
36456      var segmentTimeInfo = parseByDuration(attributes);
36457
36458      if (segmentTimeInfo.length) {
36459        segment.duration = segmentTimeInfo[0].duration;
36460        segment.timeline = segmentTimeInfo[0].timeline;
36461      }
36462    } else if (sourceDuration) {
36463      segment.duration = sourceDuration;
36464      segment.timeline = 0;
36465    } // This is used for mediaSequence
36466
36467
36468    segment.number = 0;
36469    return [segment];
36470  };
36471  /**
36472   * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
36473   * according to the sidx information given.
36474   *
36475   * playlist.sidx has metadadata about the sidx where-as the sidx param
36476   * is the parsed sidx box itself.
36477   *
36478   * @param {Object} playlist the playlist to update the sidx information for
36479   * @param {Object} sidx the parsed sidx box
36480   * @return {Object} the playlist object with the updated sidx information
36481   */
36482
36483
36484  var addSidxSegmentsToPlaylist = function addSidxSegmentsToPlaylist(playlist, sidx, baseUrl) {
36485    // Retain init segment information
36486    var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
36487
36488    var sourceDuration = playlist.sidx.duration; // Retain source timeline
36489
36490    var timeline = playlist.timeline || 0;
36491    var sidxByteRange = playlist.sidx.byterange;
36492    var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
36493
36494    var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
36495
36496    var mediaReferences = sidx.references.filter(function (r) {
36497      return r.referenceType !== 1;
36498    });
36499    var segments = [];
36500    var type = playlist.endList ? 'static' : 'dynamic'; // firstOffset is the offset from the end of the sidx box
36501
36502    var startIndex = sidxEnd + sidx.firstOffset;
36503
36504    for (var i = 0; i < mediaReferences.length; i++) {
36505      var reference = sidx.references[i]; // size of the referenced (sub)segment
36506
36507      var size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
36508      // this will be converted to seconds when generating segments
36509
36510      var duration = reference.subsegmentDuration; // should be an inclusive range
36511
36512      var endIndex = startIndex + size - 1;
36513      var indexRange = startIndex + "-" + endIndex;
36514      var attributes = {
36515        baseUrl: baseUrl,
36516        timescale: timescale,
36517        timeline: timeline,
36518        // this is used in parseByDuration
36519        periodIndex: timeline,
36520        duration: duration,
36521        sourceDuration: sourceDuration,
36522        indexRange: indexRange,
36523        type: type
36524      };
36525      var segment = segmentsFromBase(attributes)[0];
36526
36527      if (initSegment) {
36528        segment.map = initSegment;
36529      }
36530
36531      segments.push(segment);
36532      startIndex += size;
36533    }
36534
36535    playlist.segments = segments;
36536    return playlist;
36537  };
36538
36539  var generateSidxKey = function generateSidxKey(sidx) {
36540    return sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
36541  };
36542
36543  var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
36544    var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
36545      // assuming playlist IDs are the same across periods
36546      // TODO: handle multiperiod where representation sets are not the same
36547      // across periods
36548      var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
36549
36550      if (acc[name]) {
36551        var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
36552
36553
36554        if (playlist.segments[0]) {
36555          playlist.segments[0].discontinuity = true;
36556        }
36557
36558        (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
36559        // has the same contentProtection
36560
36561
36562        if (playlist.attributes.contentProtection) {
36563          acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
36564        }
36565      } else {
36566        // first Period
36567        acc[name] = playlist;
36568      }
36569
36570      return acc;
36571    }, {}));
36572    return mergedPlaylists.map(function (playlist) {
36573      playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
36574      return playlist;
36575    });
36576  };
36577
36578  var addSidxSegmentsToPlaylist$1 = function addSidxSegmentsToPlaylist$1(playlist, sidxMapping) {
36579    var sidxKey = generateSidxKey(playlist.sidx);
36580    var sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
36581
36582    if (sidxMatch) {
36583      addSidxSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
36584    }
36585
36586    return playlist;
36587  };
36588
36589  var addSidxSegmentsToPlaylists = function addSidxSegmentsToPlaylists(playlists, sidxMapping) {
36590    if (sidxMapping === void 0) {
36591      sidxMapping = {};
36592    }
36593
36594    if (!Object.keys(sidxMapping).length) {
36595      return playlists;
36596    }
36597
36598    for (var i in playlists) {
36599      playlists[i] = addSidxSegmentsToPlaylist$1(playlists[i], sidxMapping);
36600    }
36601
36602    return playlists;
36603  };
36604
36605  var formatAudioPlaylist = function formatAudioPlaylist(_ref, isAudioOnly) {
36606    var _attributes;
36607
36608    var attributes = _ref.attributes,
36609        segments = _ref.segments,
36610        sidx = _ref.sidx;
36611    var playlist = {
36612      attributes: (_attributes = {
36613        NAME: attributes.id,
36614        BANDWIDTH: attributes.bandwidth,
36615        CODECS: attributes.codecs
36616      }, _attributes['PROGRAM-ID'] = 1, _attributes),
36617      uri: '',
36618      endList: attributes.type === 'static',
36619      timeline: attributes.periodIndex,
36620      resolvedUri: '',
36621      targetDuration: attributes.duration,
36622      segments: segments,
36623      mediaSequence: segments.length ? segments[0].number : 1
36624    };
36625
36626    if (attributes.contentProtection) {
36627      playlist.contentProtection = attributes.contentProtection;
36628    }
36629
36630    if (sidx) {
36631      playlist.sidx = sidx;
36632    }
36633
36634    if (isAudioOnly) {
36635      playlist.attributes.AUDIO = 'audio';
36636      playlist.attributes.SUBTITLES = 'subs';
36637    }
36638
36639    return playlist;
36640  };
36641
36642  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
36643    var _m3u8Attributes;
36644
36645    var attributes = _ref2.attributes,
36646        segments = _ref2.segments;
36647
36648    if (typeof segments === 'undefined') {
36649      // vtt tracks may use single file in BaseURL
36650      segments = [{
36651        uri: attributes.baseUrl,
36652        timeline: attributes.periodIndex,
36653        resolvedUri: attributes.baseUrl || '',
36654        duration: attributes.sourceDuration,
36655        number: 0
36656      }]; // targetDuration should be the same duration as the only segment
36657
36658      attributes.duration = attributes.sourceDuration;
36659    }
36660
36661    var m3u8Attributes = (_m3u8Attributes = {
36662      NAME: attributes.id,
36663      BANDWIDTH: attributes.bandwidth
36664    }, _m3u8Attributes['PROGRAM-ID'] = 1, _m3u8Attributes);
36665
36666    if (attributes.codecs) {
36667      m3u8Attributes.CODECS = attributes.codecs;
36668    }
36669
36670    return {
36671      attributes: m3u8Attributes,
36672      uri: '',
36673      endList: attributes.type === 'static',
36674      timeline: attributes.periodIndex,
36675      resolvedUri: attributes.baseUrl || '',
36676      targetDuration: attributes.duration,
36677      segments: segments,
36678      mediaSequence: segments.length ? segments[0].number : 1
36679    };
36680  };
36681
36682  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping, isAudioOnly) {
36683    if (sidxMapping === void 0) {
36684      sidxMapping = {};
36685    }
36686
36687    if (isAudioOnly === void 0) {
36688      isAudioOnly = false;
36689    }
36690
36691    var mainPlaylist;
36692    var formattedPlaylists = playlists.reduce(function (a, playlist) {
36693      var role = playlist.attributes.role && playlist.attributes.role.value || '';
36694      var language = playlist.attributes.lang || '';
36695      var label = playlist.attributes.label || 'main';
36696
36697      if (language && !playlist.attributes.label) {
36698        var roleLabel = role ? " (" + role + ")" : '';
36699        label = "" + playlist.attributes.lang + roleLabel;
36700      }
36701
36702      if (!a[label]) {
36703        a[label] = {
36704          language: language,
36705          autoselect: true,
36706          "default": role === 'main',
36707          playlists: [],
36708          uri: ''
36709        };
36710      }
36711
36712      var formatted = addSidxSegmentsToPlaylist$1(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
36713      a[label].playlists.push(formatted);
36714
36715      if (typeof mainPlaylist === 'undefined' && role === 'main') {
36716        mainPlaylist = playlist;
36717        mainPlaylist["default"] = true;
36718      }
36719
36720      return a;
36721    }, {}); // if no playlists have role "main", mark the first as main
36722
36723    if (!mainPlaylist) {
36724      var firstLabel = Object.keys(formattedPlaylists)[0];
36725      formattedPlaylists[firstLabel]["default"] = true;
36726    }
36727
36728    return formattedPlaylists;
36729  };
36730
36731  var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) {
36732    if (sidxMapping === void 0) {
36733      sidxMapping = {};
36734    }
36735
36736    return playlists.reduce(function (a, playlist) {
36737      var label = playlist.attributes.lang || 'text';
36738
36739      if (!a[label]) {
36740        a[label] = {
36741          language: label,
36742          "default": false,
36743          autoselect: false,
36744          playlists: [],
36745          uri: ''
36746        };
36747      }
36748
36749      a[label].playlists.push(addSidxSegmentsToPlaylist$1(formatVttPlaylist(playlist), sidxMapping));
36750      return a;
36751    }, {});
36752  };
36753
36754  var organizeCaptionServices = function organizeCaptionServices(captionServices) {
36755    return captionServices.reduce(function (svcObj, svc) {
36756      if (!svc) {
36757        return svcObj;
36758      }
36759
36760      svc.forEach(function (service) {
36761        var channel = service.channel,
36762            language = service.language;
36763        svcObj[language] = {
36764          autoselect: false,
36765          "default": false,
36766          instreamId: channel,
36767          language: language
36768        };
36769
36770        if (service.hasOwnProperty('aspectRatio')) {
36771          svcObj[language].aspectRatio = service.aspectRatio;
36772        }
36773
36774        if (service.hasOwnProperty('easyReader')) {
36775          svcObj[language].easyReader = service.easyReader;
36776        }
36777
36778        if (service.hasOwnProperty('3D')) {
36779          svcObj[language]['3D'] = service['3D'];
36780        }
36781      });
36782      return svcObj;
36783    }, {});
36784  };
36785
36786  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
36787    var _attributes2;
36788
36789    var attributes = _ref3.attributes,
36790        segments = _ref3.segments,
36791        sidx = _ref3.sidx;
36792    var playlist = {
36793      attributes: (_attributes2 = {
36794        NAME: attributes.id,
36795        AUDIO: 'audio',
36796        SUBTITLES: 'subs',
36797        RESOLUTION: {
36798          width: attributes.width,
36799          height: attributes.height
36800        },
36801        CODECS: attributes.codecs,
36802        BANDWIDTH: attributes.bandwidth
36803      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
36804      uri: '',
36805      endList: attributes.type === 'static',
36806      timeline: attributes.periodIndex,
36807      resolvedUri: '',
36808      targetDuration: attributes.duration,
36809      segments: segments,
36810      mediaSequence: segments.length ? segments[0].number : 1
36811    };
36812
36813    if (attributes.contentProtection) {
36814      playlist.contentProtection = attributes.contentProtection;
36815    }
36816
36817    if (sidx) {
36818      playlist.sidx = sidx;
36819    }
36820
36821    return playlist;
36822  };
36823
36824  var videoOnly = function videoOnly(_ref4) {
36825    var attributes = _ref4.attributes;
36826    return attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
36827  };
36828
36829  var audioOnly = function audioOnly(_ref5) {
36830    var attributes = _ref5.attributes;
36831    return attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
36832  };
36833
36834  var vttOnly = function vttOnly(_ref6) {
36835    var attributes = _ref6.attributes;
36836    return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
36837  };
36838
36839  var toM3u8 = function toM3u8(dashPlaylists, locations, sidxMapping) {
36840    var _mediaGroups;
36841
36842    if (sidxMapping === void 0) {
36843      sidxMapping = {};
36844    }
36845
36846    if (!dashPlaylists.length) {
36847      return {};
36848    } // grab all main manifest attributes
36849
36850
36851    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
36852        duration = _dashPlaylists$0$attr.sourceDuration,
36853        type = _dashPlaylists$0$attr.type,
36854        suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay,
36855        minimumUpdatePeriod = _dashPlaylists$0$attr.minimumUpdatePeriod;
36856    var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
36857    var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
36858    var vttPlaylists = dashPlaylists.filter(vttOnly);
36859    var captions = dashPlaylists.map(function (playlist) {
36860      return playlist.attributes.captionServices;
36861    }).filter(Boolean);
36862    var manifest = {
36863      allowCache: true,
36864      discontinuityStarts: [],
36865      segments: [],
36866      endList: true,
36867      mediaGroups: (_mediaGroups = {
36868        AUDIO: {},
36869        VIDEO: {}
36870      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
36871      uri: '',
36872      duration: duration,
36873      playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
36874    };
36875
36876    if (minimumUpdatePeriod >= 0) {
36877      manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
36878    }
36879
36880    if (locations) {
36881      manifest.locations = locations;
36882    }
36883
36884    if (type === 'dynamic') {
36885      manifest.suggestedPresentationDelay = suggestedPresentationDelay;
36886    }
36887
36888    var isAudioOnly = manifest.playlists.length === 0;
36889
36890    if (audioPlaylists.length) {
36891      manifest.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly);
36892    }
36893
36894    if (vttPlaylists.length) {
36895      manifest.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
36896    }
36897
36898    if (captions.length) {
36899      manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
36900    }
36901
36902    return manifest;
36903  };
36904  /**
36905   * Calculates the R (repetition) value for a live stream (for the final segment
36906   * in a manifest where the r value is negative 1)
36907   *
36908   * @param {Object} attributes
36909   *        Object containing all inherited attributes from parent elements with attribute
36910   *        names as keys
36911   * @param {number} time
36912   *        current time (typically the total time up until the final segment)
36913   * @param {number} duration
36914   *        duration property for the given <S />
36915   *
36916   * @return {number}
36917   *        R value to reach the end of the given period
36918   */
36919
36920
36921  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
36922    var NOW = attributes.NOW,
36923        clientOffset = attributes.clientOffset,
36924        availabilityStartTime = attributes.availabilityStartTime,
36925        _attributes$timescale = attributes.timescale,
36926        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
36927        _attributes$start = attributes.start,
36928        start = _attributes$start === void 0 ? 0 : _attributes$start,
36929        _attributes$minimumUp = attributes.minimumUpdatePeriod,
36930        minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
36931    var now = (NOW + clientOffset) / 1000;
36932    var periodStartWC = availabilityStartTime + start;
36933    var periodEndWC = now + minimumUpdatePeriod;
36934    var periodDuration = periodEndWC - periodStartWC;
36935    return Math.ceil((periodDuration * timescale - time) / duration);
36936  };
36937  /**
36938   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
36939   * timing and duration
36940   *
36941   * @param {Object} attributes
36942   *        Object containing all inherited attributes from parent elements with attribute
36943   *        names as keys
36944   * @param {Object[]} segmentTimeline
36945   *        List of objects representing the attributes of each S element contained within
36946   *
36947   * @return {{number: number, duration: number, time: number, timeline: number}[]}
36948   *         List of Objects with segment timing and duration info
36949   */
36950
36951
36952  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
36953    var type = attributes.type,
36954        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
36955        minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
36956        _attributes$media = attributes.media,
36957        media = _attributes$media === void 0 ? '' : _attributes$media,
36958        sourceDuration = attributes.sourceDuration,
36959        _attributes$timescale2 = attributes.timescale,
36960        timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
36961        _attributes$startNumb = attributes.startNumber,
36962        startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
36963        timeline = attributes.periodIndex;
36964    var segments = [];
36965    var time = -1;
36966
36967    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
36968      var S = segmentTimeline[sIndex];
36969      var duration = S.d;
36970      var repeat = S.r || 0;
36971      var segmentTime = S.t || 0;
36972
36973      if (time < 0) {
36974        // first segment
36975        time = segmentTime;
36976      }
36977
36978      if (segmentTime && segmentTime > time) {
36979        // discontinuity
36980        // TODO: How to handle this type of discontinuity
36981        // timeline++ here would treat it like HLS discontuity and content would
36982        // get appended without gap
36983        // E.G.
36984        //  <S t="0" d="1" />
36985        //  <S d="1" />
36986        //  <S d="1" />
36987        //  <S t="5" d="1" />
36988        // would have $Time$ values of [0, 1, 2, 5]
36989        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
36990        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
36991        // does the value of sourceDuration consider this when calculating arbitrary
36992        // negative @r repeat value?
36993        // E.G. Same elements as above with this added at the end
36994        //  <S d="1" r="-1" />
36995        //  with a sourceDuration of 10
36996        // Would the 2 gaps be included in the time duration calculations resulting in
36997        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
36998        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
36999        time = segmentTime;
37000      }
37001
37002      var count = void 0;
37003
37004      if (repeat < 0) {
37005        var nextS = sIndex + 1;
37006
37007        if (nextS === segmentTimeline.length) {
37008          // last segment
37009          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
37010            count = getLiveRValue(attributes, time, duration);
37011          } else {
37012            // TODO: This may be incorrect depending on conclusion of TODO above
37013            count = (sourceDuration * timescale - time) / duration;
37014          }
37015        } else {
37016          count = (segmentTimeline[nextS].t - time) / duration;
37017        }
37018      } else {
37019        count = repeat + 1;
37020      }
37021
37022      var end = startNumber + segments.length + count;
37023      var number = startNumber + segments.length;
37024
37025      while (number < end) {
37026        segments.push({
37027          number: number,
37028          duration: duration / timescale,
37029          time: time,
37030          timeline: timeline
37031        });
37032        time += duration;
37033        number++;
37034      }
37035    }
37036
37037    return segments;
37038  };
37039
37040  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
37041  /**
37042   * Replaces template identifiers with corresponding values. To be used as the callback
37043   * for String.prototype.replace
37044   *
37045   * @name replaceCallback
37046   * @function
37047   * @param {string} match
37048   *        Entire match of identifier
37049   * @param {string} identifier
37050   *        Name of matched identifier
37051   * @param {string} format
37052   *        Format tag string. Its presence indicates that padding is expected
37053   * @param {string} width
37054   *        Desired length of the replaced value. Values less than this width shall be left
37055   *        zero padded
37056   * @return {string}
37057   *         Replacement for the matched identifier
37058   */
37059
37060  /**
37061   * Returns a function to be used as a callback for String.prototype.replace to replace
37062   * template identifiers
37063   *
37064   * @param {Obect} values
37065   *        Object containing values that shall be used to replace known identifiers
37066   * @param {number} values.RepresentationID
37067   *        Value of the Representation@id attribute
37068   * @param {number} values.Number
37069   *        Number of the corresponding segment
37070   * @param {number} values.Bandwidth
37071   *        Value of the Representation@bandwidth attribute.
37072   * @param {number} values.Time
37073   *        Timestamp value of the corresponding segment
37074   * @return {replaceCallback}
37075   *         Callback to be used with String.prototype.replace to replace identifiers
37076   */
37077
37078  var identifierReplacement = function identifierReplacement(values) {
37079    return function (match, identifier, format, width) {
37080      if (match === '$$') {
37081        // escape sequence
37082        return '$';
37083      }
37084
37085      if (typeof values[identifier] === 'undefined') {
37086        return match;
37087      }
37088
37089      var value = '' + values[identifier];
37090
37091      if (identifier === 'RepresentationID') {
37092        // Format tag shall not be present with RepresentationID
37093        return value;
37094      }
37095
37096      if (!format) {
37097        width = 1;
37098      } else {
37099        width = parseInt(width, 10);
37100      }
37101
37102      if (value.length >= width) {
37103        return value;
37104      }
37105
37106      return "" + new Array(width - value.length + 1).join('0') + value;
37107    };
37108  };
37109  /**
37110   * Constructs a segment url from a template string
37111   *
37112   * @param {string} url
37113   *        Template string to construct url from
37114   * @param {Obect} values
37115   *        Object containing values that shall be used to replace known identifiers
37116   * @param {number} values.RepresentationID
37117   *        Value of the Representation@id attribute
37118   * @param {number} values.Number
37119   *        Number of the corresponding segment
37120   * @param {number} values.Bandwidth
37121   *        Value of the Representation@bandwidth attribute.
37122   * @param {number} values.Time
37123   *        Timestamp value of the corresponding segment
37124   * @return {string}
37125   *         Segment url with identifiers replaced
37126   */
37127
37128
37129  var constructTemplateUrl = function constructTemplateUrl(url, values) {
37130    return url.replace(identifierPattern, identifierReplacement(values));
37131  };
37132  /**
37133   * Generates a list of objects containing timing and duration information about each
37134   * segment needed to generate segment uris and the complete segment object
37135   *
37136   * @param {Object} attributes
37137   *        Object containing all inherited attributes from parent elements with attribute
37138   *        names as keys
37139   * @param {Object[]|undefined} segmentTimeline
37140   *        List of objects representing the attributes of each S element contained within
37141   *        the SegmentTimeline element
37142   * @return {{number: number, duration: number, time: number, timeline: number}[]}
37143   *         List of Objects with segment timing and duration info
37144   */
37145
37146
37147  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
37148    if (!attributes.duration && !segmentTimeline) {
37149      // if neither @duration or SegmentTimeline are present, then there shall be exactly
37150      // one media segment
37151      return [{
37152        number: attributes.startNumber || 1,
37153        duration: attributes.sourceDuration,
37154        time: 0,
37155        timeline: attributes.periodIndex
37156      }];
37157    }
37158
37159    if (attributes.duration) {
37160      return parseByDuration(attributes);
37161    }
37162
37163    return parseByTimeline(attributes, segmentTimeline);
37164  };
37165  /**
37166   * Generates a list of segments using information provided by the SegmentTemplate element
37167   *
37168   * @param {Object} attributes
37169   *        Object containing all inherited attributes from parent elements with attribute
37170   *        names as keys
37171   * @param {Object[]|undefined} segmentTimeline
37172   *        List of objects representing the attributes of each S element contained within
37173   *        the SegmentTimeline element
37174   * @return {Object[]}
37175   *         List of segment objects
37176   */
37177
37178
37179  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
37180    var templateValues = {
37181      RepresentationID: attributes.id,
37182      Bandwidth: attributes.bandwidth || 0
37183    };
37184    var _attributes$initializ = attributes.initialization,
37185        initialization = _attributes$initializ === void 0 ? {
37186      sourceURL: '',
37187      range: ''
37188    } : _attributes$initializ;
37189    var mapSegment = urlTypeToSegment({
37190      baseUrl: attributes.baseUrl,
37191      source: constructTemplateUrl(initialization.sourceURL, templateValues),
37192      range: initialization.range
37193    });
37194    var segments = parseTemplateInfo(attributes, segmentTimeline);
37195    return segments.map(function (segment) {
37196      templateValues.Number = segment.number;
37197      templateValues.Time = segment.time;
37198      var uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
37199      // - if timescale isn't present on any level, default to 1.
37200
37201      var timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37202
37203      var presentationTimeOffset = attributes.presentationTimeOffset || 0;
37204      var presentationTime = // Even if the @t attribute is not specified for the segment, segment.time is
37205      // calculated in mpd-parser prior to this, so it's assumed to be available.
37206      attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
37207      var map = {
37208        uri: uri,
37209        timeline: segment.timeline,
37210        duration: segment.duration,
37211        resolvedUri: resolveUrl$1(attributes.baseUrl || '', uri),
37212        map: mapSegment,
37213        number: segment.number,
37214        presentationTime: presentationTime
37215      };
37216      return map;
37217    });
37218  };
37219  /**
37220   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
37221   * to an object that matches the output of a segment in videojs/mpd-parser
37222   *
37223   * @param {Object} attributes
37224   *   Object containing all inherited attributes from parent elements with attribute
37225   *   names as keys
37226   * @param {Object} segmentUrl
37227   *   <SegmentURL> node to translate into a segment object
37228   * @return {Object} translated segment object
37229   */
37230
37231
37232  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
37233    var baseUrl = attributes.baseUrl,
37234        _attributes$initializ = attributes.initialization,
37235        initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
37236    var initSegment = urlTypeToSegment({
37237      baseUrl: baseUrl,
37238      source: initialization.sourceURL,
37239      range: initialization.range
37240    });
37241    var segment = urlTypeToSegment({
37242      baseUrl: baseUrl,
37243      source: segmentUrl.media,
37244      range: segmentUrl.mediaRange
37245    });
37246    segment.map = initSegment;
37247    return segment;
37248  };
37249  /**
37250   * Generates a list of segments using information provided by the SegmentList element
37251   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
37252   * node should be translated into segment.
37253   *
37254   * @param {Object} attributes
37255   *   Object containing all inherited attributes from parent elements with attribute
37256   *   names as keys
37257   * @param {Object[]|undefined} segmentTimeline
37258   *        List of objects representing the attributes of each S element contained within
37259   *        the SegmentTimeline element
37260   * @return {Object.<Array>} list of segments
37261   */
37262
37263
37264  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
37265    var duration = attributes.duration,
37266        _attributes$segmentUr = attributes.segmentUrls,
37267        segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr,
37268        periodStart = attributes.periodStart; // Per spec (5.3.9.2.1) no way to determine segment duration OR
37269    // if both SegmentTimeline and @duration are defined, it is outside of spec.
37270
37271    if (!duration && !segmentTimeline || duration && segmentTimeline) {
37272      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
37273    }
37274
37275    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
37276      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
37277    });
37278    var segmentTimeInfo;
37279
37280    if (duration) {
37281      segmentTimeInfo = parseByDuration(attributes);
37282    }
37283
37284    if (segmentTimeline) {
37285      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
37286    }
37287
37288    var segments = segmentTimeInfo.map(function (segmentTime, index) {
37289      if (segmentUrlMap[index]) {
37290        var segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
37291        // - if timescale isn't present on any level, default to 1.
37292
37293        var timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37294
37295        var presentationTimeOffset = attributes.presentationTimeOffset || 0;
37296        segment.timeline = segmentTime.timeline;
37297        segment.duration = segmentTime.duration;
37298        segment.number = segmentTime.number;
37299        segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
37300        return segment;
37301      } // Since we're mapping we should get rid of any blank segments (in case
37302      // the given SegmentTimeline is handling for more elements than we have
37303      // SegmentURLs for).
37304
37305    }).filter(function (segment) {
37306      return segment;
37307    });
37308    return segments;
37309  };
37310
37311  var generateSegments = function generateSegments(_ref) {
37312    var attributes = _ref.attributes,
37313        segmentInfo = _ref.segmentInfo;
37314    var segmentAttributes;
37315    var segmentsFn;
37316
37317    if (segmentInfo.template) {
37318      segmentsFn = segmentsFromTemplate;
37319      segmentAttributes = merge(attributes, segmentInfo.template);
37320    } else if (segmentInfo.base) {
37321      segmentsFn = segmentsFromBase;
37322      segmentAttributes = merge(attributes, segmentInfo.base);
37323    } else if (segmentInfo.list) {
37324      segmentsFn = segmentsFromList;
37325      segmentAttributes = merge(attributes, segmentInfo.list);
37326    }
37327
37328    var segmentsInfo = {
37329      attributes: attributes
37330    };
37331
37332    if (!segmentsFn) {
37333      return segmentsInfo;
37334    }
37335
37336    var segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
37337    // must be in seconds. Since we've generated the segment list, we no longer need
37338    // @duration to be in @timescale units, so we can convert it here.
37339
37340    if (segmentAttributes.duration) {
37341      var _segmentAttributes = segmentAttributes,
37342          duration = _segmentAttributes.duration,
37343          _segmentAttributes$ti = _segmentAttributes.timescale,
37344          timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
37345      segmentAttributes.duration = duration / timescale;
37346    } else if (segments.length) {
37347      // if there is no @duration attribute, use the largest segment duration as
37348      // as target duration
37349      segmentAttributes.duration = segments.reduce(function (max, segment) {
37350        return Math.max(max, Math.ceil(segment.duration));
37351      }, 0);
37352    } else {
37353      segmentAttributes.duration = 0;
37354    }
37355
37356    segmentsInfo.attributes = segmentAttributes;
37357    segmentsInfo.segments = segments; // This is a sidx box without actual segment information
37358
37359    if (segmentInfo.base && segmentAttributes.indexRange) {
37360      segmentsInfo.sidx = segments[0];
37361      segmentsInfo.segments = [];
37362    }
37363
37364    return segmentsInfo;
37365  };
37366
37367  var toPlaylists = function toPlaylists(representations) {
37368    return representations.map(generateSegments);
37369  };
37370
vendor: 14,298 bytes, lines 37371-37795
37371  var findChildren = function findChildren(element, name) {
37372    return from(element.childNodes).filter(function (_ref) {
37373      var tagName = _ref.tagName;
37374      return tagName === name;
37375    });
37376  };
37377
37378  var getContent = function getContent(element) {
37379    return element.textContent.trim();
37380  };
37381
37382  var parseDuration = function parseDuration(str) {
37383    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
37384    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
37385    var SECONDS_IN_DAY = 24 * 60 * 60;
37386    var SECONDS_IN_HOUR = 60 * 60;
37387    var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
37388
37389    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
37390    var match = durationRegex.exec(str);
37391
37392    if (!match) {
37393      return 0;
37394    }
37395
37396    var _match$slice = match.slice(1),
37397        year = _match$slice[0],
37398        month = _match$slice[1],
37399        day = _match$slice[2],
37400        hour = _match$slice[3],
37401        minute = _match$slice[4],
37402        second = _match$slice[5];
37403
37404    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);
37405  };
37406
37407  var parseDate = function parseDate(str) {
37408    // Date format without timezone according to ISO 8601
37409    // YYY-MM-DDThh:mm:ss.ssssss
37410    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
37411    // expressed by ending with 'Z'
37412
37413    if (dateRegex.test(str)) {
37414      str += 'Z';
37415    }
37416
37417    return Date.parse(str);
37418  };
37419
37420  var parsers = {
37421    /**
37422     * Specifies the duration of the entire Media Presentation. Format is a duration string
37423     * as specified in ISO 8601
37424     *
37425     * @param {string} value
37426     *        value of attribute as a string
37427     * @return {number}
37428     *         The duration in seconds
37429     */
37430    mediaPresentationDuration: function mediaPresentationDuration(value) {
37431      return parseDuration(value);
37432    },
37433
37434    /**
37435     * Specifies the Segment availability start time for all Segments referred to in this
37436     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
37437     * time. Format is a date string as specified in ISO 8601
37438     *
37439     * @param {string} value
37440     *        value of attribute as a string
37441     * @return {number}
37442     *         The date as seconds from unix epoch
37443     */
37444    availabilityStartTime: function availabilityStartTime(value) {
37445      return parseDate(value) / 1000;
37446    },
37447
37448    /**
37449     * Specifies the smallest period between potential changes to the MPD. Format is a
37450     * duration string as specified in ISO 8601
37451     *
37452     * @param {string} value
37453     *        value of attribute as a string
37454     * @return {number}
37455     *         The duration in seconds
37456     */
37457    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
37458      return parseDuration(value);
37459    },
37460
37461    /**
37462     * Specifies the suggested presentation delay. Format is a
37463     * duration string as specified in ISO 8601
37464     *
37465     * @param {string} value
37466     *        value of attribute as a string
37467     * @return {number}
37468     *         The duration in seconds
37469     */
37470    suggestedPresentationDelay: function suggestedPresentationDelay(value) {
37471      return parseDuration(value);
37472    },
37473
37474    /**
37475     * specifices the type of mpd. Can be either "static" or "dynamic"
37476     *
37477     * @param {string} value
37478     *        value of attribute as a string
37479     *
37480     * @return {string}
37481     *         The type as a string
37482     */
37483    type: function type(value) {
37484      return value;
37485    },
37486
37487    /**
37488     * Specifies the duration of the smallest time shifting buffer for any Representation
37489     * in the MPD. Format is a duration string as specified in ISO 8601
37490     *
37491     * @param {string} value
37492     *        value of attribute as a string
37493     * @return {number}
37494     *         The duration in seconds
37495     */
37496    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
37497      return parseDuration(value);
37498    },
37499
37500    /**
37501     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
37502     * Format is a duration string as specified in ISO 8601
37503     *
37504     * @param {string} value
37505     *        value of attribute as a string
37506     * @return {number}
37507     *         The duration in seconds
37508     */
37509    start: function start(value) {
37510      return parseDuration(value);
37511    },
37512
37513    /**
37514     * Specifies the width of the visual presentation
37515     *
37516     * @param {string} value
37517     *        value of attribute as a string
37518     * @return {number}
37519     *         The parsed width
37520     */
37521    width: function width(value) {
37522      return parseInt(value, 10);
37523    },
37524
37525    /**
37526     * Specifies the height of the visual presentation
37527     *
37528     * @param {string} value
37529     *        value of attribute as a string
37530     * @return {number}
37531     *         The parsed height
37532     */
37533    height: function height(value) {
37534      return parseInt(value, 10);
37535    },
37536
37537    /**
37538     * Specifies the bitrate of the representation
37539     *
37540     * @param {string} value
37541     *        value of attribute as a string
37542     * @return {number}
37543     *         The parsed bandwidth
37544     */
37545    bandwidth: function bandwidth(value) {
37546      return parseInt(value, 10);
37547    },
37548
37549    /**
37550     * Specifies the number of the first Media Segment in this Representation in the Period
37551     *
37552     * @param {string} value
37553     *        value of attribute as a string
37554     * @return {number}
37555     *         The parsed number
37556     */
37557    startNumber: function startNumber(value) {
37558      return parseInt(value, 10);
37559    },
37560
37561    /**
37562     * Specifies the timescale in units per seconds
37563     *
37564     * @param {string} value
37565     *        value of attribute as a string
37566     * @return {number}
37567     *         The parsed timescale
37568     */
37569    timescale: function timescale(value) {
37570      return parseInt(value, 10);
37571    },
37572
37573    /**
37574     * Specifies the presentationTimeOffset.
37575     *
37576     * @param {string} value
37577     *        value of the attribute as a string
37578     *
37579     * @return {number}
37580     *         The parsed presentationTimeOffset
37581     */
37582    presentationTimeOffset: function presentationTimeOffset(value) {
37583      return parseInt(value, 10);
37584    },
37585
37586    /**
37587     * Specifies the constant approximate Segment duration
37588     * NOTE: The <Period> element also contains an @duration attribute. This duration
37589     *       specifies the duration of the Period. This attribute is currently not
37590     *       supported by the rest of the parser, however we still check for it to prevent
37591     *       errors.
37592     *
37593     * @param {string} value
37594     *        value of attribute as a string
37595     * @return {number}
37596     *         The parsed duration
37597     */
37598    duration: function duration(value) {
37599      var parsedValue = parseInt(value, 10);
37600
37601      if (isNaN(parsedValue)) {
37602        return parseDuration(value);
37603      }
37604
37605      return parsedValue;
37606    },
37607
37608    /**
37609     * Specifies the Segment duration, in units of the value of the @timescale.
37610     *
37611     * @param {string} value
37612     *        value of attribute as a string
37613     * @return {number}
37614     *         The parsed duration
37615     */
37616    d: function d(value) {
37617      return parseInt(value, 10);
37618    },
37619
37620    /**
37621     * Specifies the MPD start time, in @timescale units, the first Segment in the series
37622     * starts relative to the beginning of the Period
37623     *
37624     * @param {string} value
37625     *        value of attribute as a string
37626     * @return {number}
37627     *         The parsed time
37628     */
37629    t: function t(value) {
37630      return parseInt(value, 10);
37631    },
37632
37633    /**
37634     * Specifies the repeat count of the number of following contiguous Segments with the
37635     * same duration expressed by the value of @d
37636     *
37637     * @param {string} value
37638     *        value of attribute as a string
37639     * @return {number}
37640     *         The parsed number
37641     */
37642    r: function r(value) {
37643      return parseInt(value, 10);
37644    },
37645
37646    /**
37647     * Default parser for all other attributes. Acts as a no-op and just returns the value
37648     * as a string
37649     *
37650     * @param {string} value
37651     *        value of attribute as a string
37652     * @return {string}
37653     *         Unparsed value
37654     */
37655    DEFAULT: function DEFAULT(value) {
37656      return value;
37657    }
37658  };
37659  /**
37660   * Gets all the attributes and values of the provided node, parses attributes with known
37661   * types, and returns an object with attribute names mapped to values.
37662   *
37663   * @param {Node} el
37664   *        The node to parse attributes from
37665   * @return {Object}
37666   *         Object with all attributes of el parsed
37667   */
37668
37669  var parseAttributes = function parseAttributes(el) {
37670    if (!(el && el.attributes)) {
37671      return {};
37672    }
37673
37674    return from(el.attributes).reduce(function (a, e) {
37675      var parseFn = parsers[e.name] || parsers.DEFAULT;
37676      a[e.name] = parseFn(e.value);
37677      return a;
37678    }, {});
37679  };
37680
37681  var keySystemsMap = {
37682    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
37683    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
37684    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
37685    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
37686  };
37687  /**
37688   * Builds a list of urls that is the product of the reference urls and BaseURL values
37689   *
37690   * @param {string[]} referenceUrls
37691   *        List of reference urls to resolve to
37692   * @param {Node[]} baseUrlElements
37693   *        List of BaseURL nodes from the mpd
37694   * @return {string[]}
37695   *         List of resolved urls
37696   */
37697
37698  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
37699    if (!baseUrlElements.length) {
37700      return referenceUrls;
37701    }
37702
37703    return flatten(referenceUrls.map(function (reference) {
37704      return baseUrlElements.map(function (baseUrlElement) {
37705        return resolveUrl$1(reference, getContent(baseUrlElement));
37706      });
37707    }));
37708  };
37709  /**
37710   * Contains all Segment information for its containing AdaptationSet
37711   *
37712   * @typedef {Object} SegmentInformation
37713   * @property {Object|undefined} template
37714   *           Contains the attributes for the SegmentTemplate node
37715   * @property {Object[]|undefined} segmentTimeline
37716   *           Contains a list of atrributes for each S node within the SegmentTimeline node
37717   * @property {Object|undefined} list
37718   *           Contains the attributes for the SegmentList node
37719   * @property {Object|undefined} base
37720   *           Contains the attributes for the SegmentBase node
37721   */
37722
37723  /**
37724   * Returns all available Segment information contained within the AdaptationSet node
37725   *
37726   * @param {Node} adaptationSet
37727   *        The AdaptationSet node to get Segment information from
37728   * @return {SegmentInformation}
37729   *         The Segment information contained within the provided AdaptationSet
37730   */
37731
37732
37733  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
37734    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
37735    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
37736    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
37737      return merge({
37738        tag: 'SegmentURL'
37739      }, parseAttributes(s));
37740    });
37741    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
37742    var segmentTimelineParentNode = segmentList || segmentTemplate;
37743    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
37744    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
37745    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
37746    // @initialization and an <Initialization> node.  @initialization can be templated,
37747    // while the node can have a url and range specified.  If the <SegmentTemplate> has
37748    // both @initialization and an <Initialization> subelement we opt to override with
37749    // the node, as this interaction is not defined in the spec.
37750
37751    var template = segmentTemplate && parseAttributes(segmentTemplate);
37752
37753    if (template && segmentInitialization) {
37754      template.initialization = segmentInitialization && parseAttributes(segmentInitialization);
37755    } else if (template && template.initialization) {
37756      // If it is @initialization we convert it to an object since this is the format that
37757      // later functions will rely on for the initialization segment.  This is only valid
37758      // for <SegmentTemplate>
37759      template.initialization = {
37760        sourceURL: template.initialization
37761      };
37762    }
37763
37764    var segmentInfo = {
37765      template: template,
37766      segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
37767        return parseAttributes(s);
37768      }),
37769      list: segmentList && merge(parseAttributes(segmentList), {
37770        segmentUrls: segmentUrls,
37771        initialization: parseAttributes(segmentInitialization)
37772      }),
37773      base: segmentBase && merge(parseAttributes(segmentBase), {
37774        initialization: parseAttributes(segmentInitialization)
37775      })
37776    };
37777    Object.keys(segmentInfo).forEach(function (key) {
37778      if (!segmentInfo[key]) {
37779        delete segmentInfo[key];
37780      }
37781    });
37782    return segmentInfo;
37783  };
37784  /**
37785   * Contains Segment information and attributes needed to construct a Playlist object
37786   * from a Representation
37787   *
37788   * @typedef {Object} RepresentationInformation
37789   * @property {SegmentInformation} segmentInfo
37790   *           Segment information for this Representation
37791   * @property {Object} attributes
37792   *           Inherited attributes for this Representation
37793   */
37794
37795  /**
37796   * Maps a Representation node to an object containing Segment information and attributes
37797   *
37798   * @name inheritBaseUrlsCallback
37799   * @function
37800   * @param {Node} representation
37801   *        Representation node from the mpd
37802   * @return {RepresentationInformation}
37803   *         Representation information needed to construct a Playlist object
37804   */
37805
37806  /**
37807   * Returns a callback for Array.prototype.map for mapping Representation nodes to
37808   * Segment information and attributes using inherited BaseURL nodes.
37809   *
37810   * @param {Object} adaptationSetAttributes
37811   *        Contains attributes inherited by the AdaptationSet
37812   * @param {string[]} adaptationSetBaseUrls
37813   *        Contains list of resolved base urls inherited by the AdaptationSet
37814   * @param {SegmentInformation} adaptationSetSegmentInfo
37815   *        Contains Segment information for the AdaptationSet
37816   * @return {inheritBaseUrlsCallback}
37817   *         Callback map function
37818   */
37819
37820
37821  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
37822    return function (representation) {
37823      var repBaseUrlElements = findChildren(representation, 'BaseURL');
37824      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
37825      var attributes = merge(adaptationSetAttributes, parseAttributes(representation));
37826      var representationSegmentInfo = getSegmentInformation(representation);
37827      return repBaseUrls.map(function (baseUrl) {
37828        return {
37829          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
37830          attributes: merge(attributes, {
37831            baseUrl: baseUrl
37832          })
37833        };
37834      });
37835    };
37836  };
37837  /**
37838   * Tranforms a series of content protection nodes to
37839   * an object containing pssh data by key system
37840   *
37841   * @param {Node[]} contentProtectionNodes
37842   *        Content protection nodes
37843   * @return {Object}
37844   *        Object containing pssh data by key system
37845   */
37846
37847
37848  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
37849    return contentProtectionNodes.reduce(function (acc, node) {
37850      var attributes = parseAttributes(node);
37851      var keySystem = keySystemsMap[attributes.schemeIdUri];
37852
37853      if (keySystem) {
37854        acc[keySystem] = {
37855          attributes: attributes
37856        };
37857        var psshNode = findChildren(node, 'cenc:pssh')[0];
37858
37859        if (psshNode) {
37860          var pssh = getContent(psshNode);
37861          var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
37862          acc[keySystem].pssh = psshBuffer;
37863        }
37864      }
37865
37866      return acc;
37867    }, {});
37868  }; // defined in ANSI_SCTE 214-1 2016
37869
37870
37871  var parseCaptionServiceMetadata = function parseCaptionServiceMetadata(service) {
37872    // 608 captions
37873    if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
37874      var values = service.value.split(';');
37875      return values.map(function (value) {
37876        var channel;
37877        var language; // default language to value
37878
37879        language = value;
37880
37881        if (/^CC\d=/.test(value)) {
37882          var _value$split = value.split('=');
37883
37884          channel = _value$split[0];
37885          language = _value$split[1];
37886        } else if (/^CC\d$/.test(value)) {
37887          channel = value;
37888        }
37889
37890        return {
37891          channel: channel,
37892          language: language
37893        };
37894      });
37895    } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
37896      var _values = service.value.split(';');
37897
37898      return _values.map(function (value) {
37899        var flags = {
37900          // service or channel number 1-63
37901          'channel': undefined,
37902          // language is a 3ALPHA per ISO 639.2/B
37903          // field is required
37904          'language': undefined,
37905          // BIT 1/0 or ?
37906          // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
37907          'aspectRatio': 1,
37908          // BIT 1/0
37909          // easy reader flag indicated the text is tailed to the needs of beginning readers
37910          // default 0, or off
37911          'easyReader': 0,
37912          // BIT 1/0
37913          // If 3d metadata is present (CEA-708.1) then 1
37914          // default 0
37915          '3D': 0
37916        };
37917
37918        if (/=/.test(value)) {
37919          var _value$split2 = value.split('='),
37920              channel = _value$split2[0],
37921              _value$split2$ = _value$split2[1],
37922              opts = _value$split2$ === void 0 ? '' : _value$split2$;
37923
37924          flags.channel = channel;
37925          flags.language = value;
37926          opts.split(',').forEach(function (opt) {
37927            var _opt$split = opt.split(':'),
37928                name = _opt$split[0],
37929                val = _opt$split[1];
37930
37931            if (name === 'lang') {
37932              flags.language = val; // er for easyReadery
37933            } else if (name === 'er') {
37934              flags.easyReader = Number(val); // war for wide aspect ratio
37935            } else if (name === 'war') {
37936              flags.aspectRatio = Number(val);
37937            } else if (name === '3D') {
37938              flags['3D'] = Number(val);
37939            }
37940          });
37941        } else {
37942          flags.language = value;
37943        }
37944
37945        if (flags.channel) {
37946          flags.channel = 'SERVICE' + flags.channel;
37947        }
37948
37949        return flags;
37950      });
37951    }
37952  };
37953  /**
37954   * Maps an AdaptationSet node to a list of Representation information objects
37955   *
37956   * @name toRepresentationsCallback
37957   * @function
37958   * @param {Node} adaptationSet
37959   *        AdaptationSet node from the mpd
37960   * @return {RepresentationInformation[]}
37961   *         List of objects containing Representaion information
37962   */
37963
37964  /**
37965   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
37966   * Representation information objects
37967   *
37968   * @param {Object} periodAttributes
37969   *        Contains attributes inherited by the Period
37970   * @param {string[]} periodBaseUrls
37971   *        Contains list of resolved base urls inherited by the Period
37972   * @param {string[]} periodSegmentInfo
37973   *        Contains Segment Information at the period level
37974   * @return {toRepresentationsCallback}
37975   *         Callback map function
37976   */
37977
37978
37979  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
37980    return function (adaptationSet) {
37981      var adaptationSetAttributes = parseAttributes(adaptationSet);
37982      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
37983      var role = findChildren(adaptationSet, 'Role')[0];
37984      var roleAttributes = {
37985        role: parseAttributes(role)
37986      };
37987      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
37988      var accessibility = findChildren(adaptationSet, 'Accessibility')[0];
37989      var captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
37990
37991      if (captionServices) {
37992        attrs = merge(attrs, {
37993          captionServices: captionServices
37994        });
37995      }
37996
37997      var label = findChildren(adaptationSet, 'Label')[0];
37998
37999      if (label && label.childNodes.length) {
38000        var labelVal = label.childNodes[0].nodeValue.trim();
38001        attrs = merge(attrs, {
38002          label: labelVal
38003        });
38004      }
38005
38006      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
38007
38008      if (Object.keys(contentProtection).length) {
38009        attrs = merge(attrs, {
38010          contentProtection: contentProtection
38011        });
38012      }
38013
38014      var segmentInfo = getSegmentInformation(adaptationSet);
38015      var representations = findChildren(adaptationSet, 'Representation');
38016      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
38017      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
38018    };
38019  };
38020  /**
38021   * Contains all period information for mapping nodes onto adaptation sets.
38022   *
38023   * @typedef {Object} PeriodInformation
38024   * @property {Node} period.node
38025   *           Period node from the mpd
38026   * @property {Object} period.attributes
38027   *           Parsed period attributes from node plus any added
38028   */
38029
38030  /**
38031   * Maps a PeriodInformation object to a list of Representation information objects for all
38032   * AdaptationSet nodes contained within the Period.
38033   *
38034   * @name toAdaptationSetsCallback
38035   * @function
38036   * @param {PeriodInformation} period
38037   *        Period object containing necessary period information
38038   * @param {number} periodIndex
38039   *        Index of the Period within the mpd
38040   * @return {RepresentationInformation[]}
38041   *         List of objects containing Representaion information
38042   */
38043
38044  /**
38045   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
38046   * Representation information objects
38047   *
38048   * @param {Object} mpdAttributes
38049   *        Contains attributes inherited by the mpd
38050   * @param {string[]} mpdBaseUrls
38051   *        Contains list of resolved base urls inherited by the mpd
38052   * @return {toAdaptationSetsCallback}
38053   *         Callback map function
38054   */
38055
38056
38057  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
38058    return function (period, index) {
38059      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
38060      var parsedPeriodId = parseInt(period.attributes.id, 10); // fallback to mapping index if Period@id is not a number
38061
38062      var periodIndex = window_1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
38063      var periodAttributes = merge(mpdAttributes, {
38064        periodIndex: periodIndex,
38065        periodStart: period.attributes.start
38066      });
38067
38068      if (typeof period.attributes.duration === 'number') {
38069        periodAttributes.periodDuration = period.attributes.duration;
38070      }
38071
38072      var adaptationSets = findChildren(period.node, 'AdaptationSet');
38073      var periodSegmentInfo = getSegmentInformation(period.node);
38074      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
38075    };
38076  };
38077  /**
38078   * Gets Period@start property for a given period.
38079   *
38080   * @param {Object} options
38081   *        Options object
38082   * @param {Object} options.attributes
38083   *        Period attributes
38084   * @param {Object} [options.priorPeriodAttributes]
38085   *        Prior period attributes (if prior period is available)
38086   * @param {string} options.mpdType
38087   *        The MPD@type these periods came from
38088   * @return {number|null}
38089   *         The period start, or null if it's an early available period or error
38090   */
38091
38092
38093  var getPeriodStart = function getPeriodStart(_ref) {
38094    var attributes = _ref.attributes,
38095        priorPeriodAttributes = _ref.priorPeriodAttributes,
38096        mpdType = _ref.mpdType; // Summary of period start time calculation from DASH spec section 5.3.2.1
38097    //
38098    // A period's start is the first period's start + time elapsed after playing all
38099    // prior periods to this one. Periods continue one after the other in time (without
38100    // gaps) until the end of the presentation.
38101    //
38102    // The value of Period@start should be:
38103    // 1. if Period@start is present: value of Period@start
38104    // 2. if previous period exists and it has @duration: previous Period@start +
38105    //    previous Period@duration
38106    // 3. if this is first period and MPD@type is 'static': 0
38107    // 4. in all other cases, consider the period an "early available period" (note: not
38108    //    currently supported)
38109    // (1)
38110
38111    if (typeof attributes.start === 'number') {
38112      return attributes.start;
38113    } // (2)
38114
38115
38116    if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
38117      return priorPeriodAttributes.start + priorPeriodAttributes.duration;
38118    } // (3)
38119
38120
38121    if (!priorPeriodAttributes && mpdType === 'static') {
38122      return 0;
38123    } // (4)
38124    // There is currently no logic for calculating the Period@start value if there is
38125    // no Period@start or prior Period@start and Period@duration available. This is not made
38126    // explicit by the DASH interop guidelines or the DASH spec, however, since there's
38127    // nothing about any other resolution strategies, it's implied. Thus, this case should
38128    // be considered an early available period, or error, and null should suffice for both
38129    // of those cases.
38130
38131
38132    return null;
38133  };
38134  /**
38135   * Traverses the mpd xml tree to generate a list of Representation information objects
38136   * that have inherited attributes from parent nodes
38137   *
38138   * @param {Node} mpd
38139   *        The root node of the mpd
38140   * @param {Object} options
38141   *        Available options for inheritAttributes
38142   * @param {string} options.manifestUri
38143   *        The uri source of the mpd
38144   * @param {number} options.NOW
38145   *        Current time per DASH IOP.  Default is current time in ms since epoch
38146   * @param {number} options.clientOffset
38147   *        Client time difference from NOW (in milliseconds)
38148   * @return {RepresentationInformation[]}
38149   *         List of objects containing Representation information
38150   */
38151
38152
38153  var inheritAttributes = function inheritAttributes(mpd, options) {
38154    if (options === void 0) {
38155      options = {};
38156    }
38157
38158    var _options = options,
38159        _options$manifestUri = _options.manifestUri,
38160        manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
38161        _options$NOW = _options.NOW,
38162        NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
38163        _options$clientOffset = _options.clientOffset,
38164        clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
38165    var periodNodes = findChildren(mpd, 'Period');
38166
38167    if (!periodNodes.length) {
38168      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
38169    }
38170
38171    var locations = findChildren(mpd, 'Location');
38172    var mpdAttributes = parseAttributes(mpd);
38173    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
38174
38175    mpdAttributes.type = mpdAttributes.type || 'static';
38176    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
38177    mpdAttributes.NOW = NOW;
38178    mpdAttributes.clientOffset = clientOffset;
38179
38180    if (locations.length) {
38181      mpdAttributes.locations = locations.map(getContent);
38182    }
38183
38184    var periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
38185    // adding properties that require looking at prior periods is to parse attributes and add
38186    // missing ones before toAdaptationSets is called. If more such properties are added, it
38187    // may be better to refactor toAdaptationSets.
38188
38189    periodNodes.forEach(function (node, index) {
38190      var attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
38191      // for this period.
38192
38193      var priorPeriod = periods[index - 1];
38194      attributes.start = getPeriodStart({
38195        attributes: attributes,
38196        priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
38197        mpdType: mpdAttributes.type
38198      });
38199      periods.push({
38200        node: node,
38201        attributes: attributes
38202      });
38203    });
38204    return {
38205      locations: mpdAttributes.locations,
38206      representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)))
38207    };
38208  };
38209
38210  var stringToMpdXml = function stringToMpdXml(manifestString) {
38211    if (manifestString === '') {
38212      throw new Error(errors.DASH_EMPTY_MANIFEST);
38213    }
38214
38215    var parser = new domParser_3();
38216    var xml;
38217    var mpd;
38218
38219    try {
38220      xml = parser.parseFromString(manifestString, 'application/xml');
38221      mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
38222    } catch (e) {// ie 11 throwsw on invalid xml
38223    }
38224
38225    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
38226      throw new Error(errors.DASH_INVALID_XML);
38227    }
38228
38229    return mpd;
38230  };
38231  /**
38232   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38233   *
38234   * @param {string} mpd
38235   *        XML string of the MPD manifest
38236   * @return {Object|null}
38237   *         Attributes of UTCTiming node specified in the manifest. Null if none found
38238   */
38239
38240
38241  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
38242    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
38243
38244    if (!UTCTimingNode) {
38245      return null;
38246    }
38247
38248    var attributes = parseAttributes(UTCTimingNode);
38249
38250    switch (attributes.schemeIdUri) {
38251      case 'urn:mpeg:dash:utc:http-head:2014':
38252      case 'urn:mpeg:dash:utc:http-head:2012':
38253        attributes.method = 'HEAD';
38254        break;
38255
38256      case 'urn:mpeg:dash:utc:http-xsdate:2014':
38257      case 'urn:mpeg:dash:utc:http-iso:2014':
38258      case 'urn:mpeg:dash:utc:http-xsdate:2012':
38259      case 'urn:mpeg:dash:utc:http-iso:2012':
38260        attributes.method = 'GET';
38261        break;
38262
38263      case 'urn:mpeg:dash:utc:direct:2014':
38264      case 'urn:mpeg:dash:utc:direct:2012':
38265        attributes.method = 'DIRECT';
38266        attributes.value = Date.parse(attributes.value);
38267        break;
38268
38269      case 'urn:mpeg:dash:utc:http-ntp:2014':
38270      case 'urn:mpeg:dash:utc:ntp:2014':
38271      case 'urn:mpeg:dash:utc:sntp:2014':
38272      default:
38273        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
38274    }
38275
38276    return attributes;
38277  };
38278
38279  var parse = function parse(manifestString, options) {
38280    if (options === void 0) {
38281      options = {};
38282    }
38283
38284    var parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
38285    var playlists = toPlaylists(parsedManifestInfo.representationInfo);
38286    return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping);
38287  };
38288  /**
38289   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38290   *
38291   * @param {string} manifestString
38292   *        XML string of the MPD manifest
38293   * @return {Object|null}
38294   *         Attributes of UTCTiming node specified in the manifest. Null if none found
38295   */
38296
38297
38298  var parseUTCTiming = function parseUTCTiming(manifestString) {
38299    return parseUTCTimingScheme(stringToMpdXml(manifestString));
38300  };
38301
38302  var MAX_UINT32 = Math.pow(2, 32);
38303
38304  var parseSidx = function parseSidx(data) {
38305    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
38306        result = {
38307      version: data[0],
38308      flags: new Uint8Array(data.subarray(1, 4)),
38309      references: [],
38310      referenceId: view.getUint32(4),
38311      timescale: view.getUint32(8)
38312    },
38313        i = 12;
38314
38315    if (result.version === 0) {
38316      result.earliestPresentationTime = view.getUint32(i);
38317      result.firstOffset = view.getUint32(i + 4);
38318      i += 8;
38319    } else {
38320      // read 64 bits
38321      result.earliestPresentationTime = view.getUint32(i) * MAX_UINT32 + view.getUint32(i + 4);
38322      result.firstOffset = view.getUint32(i + 8) * MAX_UINT32 + view.getUint32(i + 12);
38323      i += 16;
38324    }
38325
38326    i += 2; // reserved
38327
38328    var referenceCount = view.getUint16(i);
38329    i += 2; // start of references
38330
38331    for (; referenceCount > 0; i += 12, referenceCount--) {
38332      result.references.push({
38333        referenceType: (data[i] & 0x80) >>> 7,
38334        referencedSize: view.getUint32(i) & 0x7FFFFFFF,
38335        subsegmentDuration: view.getUint32(i + 4),
38336        startsWithSap: !!(data[i + 8] & 0x80),
38337        sapType: (data[i + 8] & 0x70) >>> 4,
38338        sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
38339      });
38340    }
38341
38342    return result;
38343  };
38344
38345  var parseSidx_1 = parseSidx;
38346
38347  // we used to do this with log2 but BigInt does not support builtin math
vendor: 22,355 bytes, lines 38348-39064
38348  // Math.ceil(log2(x));
38349
38350
38351  var countBits = function countBits(x) {
38352    return x.toString(2).length;
38353  }; // count the number of whole bytes it would take to represent a number
38354
38355  var countBytes = function countBytes(x) {
38356    return Math.ceil(countBits(x) / 8);
38357  };
38358  var isTypedArray = function isTypedArray(obj) {
38359    return ArrayBuffer.isView(obj);
38360  };
38361  var toUint8 = function toUint8(bytes) {
38362    if (bytes instanceof Uint8Array) {
38363      return bytes;
38364    }
38365
38366    if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
38367      // any non-number or NaN leads to empty uint8array
38368      // eslint-disable-next-line
38369      if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
38370        bytes = 0;
38371      } else {
38372        bytes = [bytes];
38373      }
38374    }
38375
38376    return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
38377  };
38378  var BigInt = window_1.BigInt || Number;
38379  var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
38380  var bytesToNumber = function bytesToNumber(bytes, _temp) {
38381    var _ref = _temp === void 0 ? {} : _temp,
38382        _ref$signed = _ref.signed,
38383        signed = _ref$signed === void 0 ? false : _ref$signed,
38384        _ref$le = _ref.le,
38385        le = _ref$le === void 0 ? false : _ref$le;
38386
38387    bytes = toUint8(bytes);
38388    var fn = le ? 'reduce' : 'reduceRight';
38389    var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
38390    var number = obj.call(bytes, function (total, _byte, i) {
38391      var exponent = le ? i : Math.abs(i + 1 - bytes.length);
38392      return total + BigInt(_byte) * BYTE_TABLE[exponent];
38393    }, BigInt(0));
38394
38395    if (signed) {
38396      var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
38397      number = BigInt(number);
38398
38399      if (number > max) {
38400        number -= max;
38401        number -= max;
38402        number -= BigInt(2);
38403      }
38404    }
38405
38406    return Number(number);
38407  };
38408  var numberToBytes = function numberToBytes(number, _temp2) {
38409    var _ref2 = _temp2 === void 0 ? {} : _temp2,
38410        _ref2$le = _ref2.le,
38411        le = _ref2$le === void 0 ? false : _ref2$le; // eslint-disable-next-line
38412
38413
38414    if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
38415      number = 0;
38416    }
38417
38418    number = BigInt(number);
38419    var byteCount = countBytes(number);
38420    var bytes = new Uint8Array(new ArrayBuffer(byteCount));
38421
38422    for (var i = 0; i < byteCount; i++) {
38423      var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
38424      bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
38425
38426      if (number < 0) {
38427        bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
38428        bytes[byteIndex] -= i === 0 ? 1 : 2;
38429      }
38430    }
38431
38432    return bytes;
38433  };
38434  var stringToBytes = function stringToBytes(string, stringIsBytes) {
38435    if (typeof string !== 'string' && string && typeof string.toString === 'function') {
38436      string = string.toString();
38437    }
38438
38439    if (typeof string !== 'string') {
38440      return new Uint8Array();
38441    } // If the string already is bytes, we don't have to do this
38442    // otherwise we do this so that we split multi length characters
38443    // into individual bytes
38444
38445
38446    if (!stringIsBytes) {
38447      string = unescape(encodeURIComponent(string));
38448    }
38449
38450    var view = new Uint8Array(string.length);
38451
38452    for (var i = 0; i < string.length; i++) {
38453      view[i] = string.charCodeAt(i);
38454    }
38455
38456    return view;
38457  };
38458  var concatTypedArrays = function concatTypedArrays() {
38459    for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
38460      buffers[_key] = arguments[_key];
38461    }
38462
38463    buffers = buffers.filter(function (b) {
38464      return b && (b.byteLength || b.length) && typeof b !== 'string';
38465    });
38466
38467    if (buffers.length <= 1) {
38468      // for 0 length we will return empty uint8
38469      // for 1 length we return the first uint8
38470      return toUint8(buffers[0]);
38471    }
38472
38473    var totalLen = buffers.reduce(function (total, buf, i) {
38474      return total + (buf.byteLength || buf.length);
38475    }, 0);
38476    var tempBuffer = new Uint8Array(totalLen);
38477    var offset = 0;
38478    buffers.forEach(function (buf) {
38479      buf = toUint8(buf);
38480      tempBuffer.set(buf, offset);
38481      offset += buf.byteLength;
38482    });
38483    return tempBuffer;
38484  };
38485  /**
38486   * Check if the bytes "b" are contained within bytes "a".
38487   *
38488   * @param {Uint8Array|Array} a
38489   *        Bytes to check in
38490   *
38491   * @param {Uint8Array|Array} b
38492   *        Bytes to check for
38493   *
38494   * @param {Object} options
38495   *        options
38496   *
38497   * @param {Array|Uint8Array} [offset=0]
38498   *        offset to use when looking at bytes in a
38499   *
38500   * @param {Array|Uint8Array} [mask=[]]
38501   *        mask to use on bytes before comparison.
38502   *
38503   * @return {boolean}
38504   *         If all bytes in b are inside of a, taking into account
38505   *         bit masks.
38506   */
38507
38508  var bytesMatch = function bytesMatch(a, b, _temp3) {
38509    var _ref3 = _temp3 === void 0 ? {} : _temp3,
38510        _ref3$offset = _ref3.offset,
38511        offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
38512        _ref3$mask = _ref3.mask,
38513        mask = _ref3$mask === void 0 ? [] : _ref3$mask;
38514
38515    a = toUint8(a);
38516    b = toUint8(b); // ie 11 does not support uint8 every
38517
38518    var fn = b.every ? b.every : Array.prototype.every;
38519    return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
38520    fn.call(b, function (bByte, i) {
38521      var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
38522      return bByte === aByte;
38523    });
38524  };
38525
38526  var ID3 = toUint8([0x49, 0x44, 0x33]);
38527  var getId3Size = function getId3Size(bytes, offset) {
38528    if (offset === void 0) {
38529      offset = 0;
38530    }
38531
38532    bytes = toUint8(bytes);
38533    var flags = bytes[offset + 5];
38534    var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
38535    var footerPresent = (flags & 16) >> 4;
38536
38537    if (footerPresent) {
38538      return returnSize + 20;
38539    }
38540
38541    return returnSize + 10;
38542  };
38543  var getId3Offset = function getId3Offset(bytes, offset) {
38544    if (offset === void 0) {
38545      offset = 0;
38546    }
38547
38548    bytes = toUint8(bytes);
38549
38550    if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, {
38551      offset: offset
38552    })) {
38553      return offset;
38554    }
38555
38556    offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
38557    // have multiple ID3 tag sections even though
38558    // they should not.
38559
38560    return getId3Offset(bytes, offset);
38561  };
38562
38563  var normalizePath$1 = function normalizePath(path) {
38564    if (typeof path === 'string') {
38565      return stringToBytes(path);
38566    }
38567
38568    if (typeof path === 'number') {
38569      return path;
38570    }
38571
38572    return path;
38573  };
38574
38575  var normalizePaths$1 = function normalizePaths(paths) {
38576    if (!Array.isArray(paths)) {
38577      return [normalizePath$1(paths)];
38578    }
38579
38580    return paths.map(function (p) {
38581      return normalizePath$1(p);
38582    });
38583  };
38584  /**
38585   * find any number of boxes by name given a path to it in an iso bmff
38586   * such as mp4.
38587   *
38588   * @param {TypedArray} bytes
38589   *        bytes for the iso bmff to search for boxes in
38590   *
38591   * @param {Uint8Array[]|string[]|string|Uint8Array} name
38592   *        An array of paths or a single path representing the name
38593   *        of boxes to search through in bytes. Paths may be
38594   *        uint8 (character codes) or strings.
38595   *
38596   * @param {boolean} [complete=false]
38597   *        Should we search only for complete boxes on the final path.
38598   *        This is very useful when you do not want to get back partial boxes
38599   *        in the case of streaming files.
38600   *
38601   * @return {Uint8Array[]}
38602   *         An array of the end paths that we found.
38603   */
38604
38605  var findBox = function findBox(bytes, paths, complete) {
38606    if (complete === void 0) {
38607      complete = false;
38608    }
38609
38610    paths = normalizePaths$1(paths);
38611    bytes = toUint8(bytes);
38612    var results = [];
38613
38614    if (!paths.length) {
38615      // short-circuit the search for empty paths
38616      return results;
38617    }
38618
38619    var i = 0;
38620
38621    while (i < bytes.length) {
38622      var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
38623      var type = bytes.subarray(i + 4, i + 8); // invalid box format.
38624
38625      if (size === 0) {
38626        break;
38627      }
38628
38629      var end = i + size;
38630
38631      if (end > bytes.length) {
38632        // this box is bigger than the number of bytes we have
38633        // and complete is set, we cannot find any more boxes.
38634        if (complete) {
38635          break;
38636        }
38637
38638        end = bytes.length;
38639      }
38640
38641      var data = bytes.subarray(i + 8, end);
38642
38643      if (bytesMatch(type, paths[0])) {
38644        if (paths.length === 1) {
38645          // this is the end of the path and we've found the box we were
38646          // looking for
38647          results.push(data);
38648        } else {
38649          // recursively search for the next box along the path
38650          results.push.apply(results, findBox(data, paths.slice(1), complete));
38651        }
38652      }
38653
38654      i = end;
38655    } // we've finished searching all of bytes
38656
38657
38658    return results;
38659  };
38660
38661  // https://matroska-org.github.io/libebml/specs.html
38662  // https://www.matroska.org/technical/elements.html
38663  // https://www.webmproject.org/docs/container/
38664
38665  var EBML_TAGS = {
38666    EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]),
38667    DocType: toUint8([0x42, 0x82]),
38668    Segment: toUint8([0x18, 0x53, 0x80, 0x67]),
38669    SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]),
38670    Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]),
38671    Track: toUint8([0xAE]),
38672    TrackNumber: toUint8([0xd7]),
38673    DefaultDuration: toUint8([0x23, 0xe3, 0x83]),
38674    TrackEntry: toUint8([0xAE]),
38675    TrackType: toUint8([0x83]),
38676    FlagDefault: toUint8([0x88]),
38677    CodecID: toUint8([0x86]),
38678    CodecPrivate: toUint8([0x63, 0xA2]),
38679    VideoTrack: toUint8([0xe0]),
38680    AudioTrack: toUint8([0xe1]),
38681    // Not used yet, but will be used for live webm/mkv
38682    // see https://www.matroska.org/technical/basics.html#block-structure
38683    // see https://www.matroska.org/technical/basics.html#simpleblock-structure
38684    Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]),
38685    Timestamp: toUint8([0xE7]),
38686    TimestampScale: toUint8([0x2A, 0xD7, 0xB1]),
38687    BlockGroup: toUint8([0xA0]),
38688    BlockDuration: toUint8([0x9B]),
38689    Block: toUint8([0xA1]),
38690    SimpleBlock: toUint8([0xA3])
38691  };
38692  /**
38693   * This is a simple table to determine the length
38694   * of things in ebml. The length is one based (starts at 1,
38695   * rather than zero) and for every zero bit before a one bit
38696   * we add one to length. We also need this table because in some
38697   * case we have to xor all the length bits from another value.
38698   */
38699
38700  var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
38701
38702  var getLength = function getLength(_byte) {
38703    var len = 1;
38704
38705    for (var i = 0; i < LENGTH_TABLE.length; i++) {
38706      if (_byte & LENGTH_TABLE[i]) {
38707        break;
38708      }
38709
38710      len++;
38711    }
38712
38713    return len;
38714  }; // length in ebml is stored in the first 4 to 8 bits
38715  // of the first byte. 4 for the id length and 8 for the
38716  // data size length. Length is measured by converting the number to binary
38717  // then 1 + the number of zeros before a 1 is encountered starting
38718  // from the left.
38719
38720
38721  var getvint = function getvint(bytes, offset, removeLength, signed) {
38722    if (removeLength === void 0) {
38723      removeLength = true;
38724    }
38725
38726    if (signed === void 0) {
38727      signed = false;
38728    }
38729
38730    var length = getLength(bytes[offset]);
38731    var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
38732    // as they will be modified below to remove the dataSizeLen bits and we do not
38733    // want to modify the original data. normally we could just call slice on
38734    // uint8array but ie 11 does not support that...
38735
38736    if (removeLength) {
38737      valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
38738      valueBytes[0] ^= LENGTH_TABLE[length - 1];
38739    }
38740
38741    return {
38742      length: length,
38743      value: bytesToNumber(valueBytes, {
38744        signed: signed
38745      }),
38746      bytes: valueBytes
38747    };
38748  };
38749
38750  var normalizePath = function normalizePath(path) {
38751    if (typeof path === 'string') {
38752      return path.match(/.{1,2}/g).map(function (p) {
38753        return normalizePath(p);
38754      });
38755    }
38756
38757    if (typeof path === 'number') {
38758      return numberToBytes(path);
38759    }
38760
38761    return path;
38762  };
38763
38764  var normalizePaths = function normalizePaths(paths) {
38765    if (!Array.isArray(paths)) {
38766      return [normalizePath(paths)];
38767    }
38768
38769    return paths.map(function (p) {
38770      return normalizePath(p);
38771    });
38772  };
38773
38774  var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
38775    if (offset >= bytes.length) {
38776      return bytes.length;
38777    }
38778
38779    var innerid = getvint(bytes, offset, false);
38780
38781    if (bytesMatch(id.bytes, innerid.bytes)) {
38782      return offset;
38783    }
38784
38785    var dataHeader = getvint(bytes, offset + innerid.length);
38786    return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
38787  };
38788  /**
38789   * Notes on the EBLM format.
38790   *
38791   * EBLM uses "vints" tags. Every vint tag contains
38792   * two parts
38793   *
38794   * 1. The length from the first byte. You get this by
38795   *    converting the byte to binary and counting the zeros
38796   *    before a 1. Then you add 1 to that. Examples
38797   *    00011111 = length 4 because there are 3 zeros before a 1.
38798   *    00100000 = length 3 because there are 2 zeros before a 1.
38799   *    00000011 = length 7 because there are 6 zeros before a 1.
38800   *
38801   * 2. The bits used for length are removed from the first byte
38802   *    Then all the bytes are merged into a value. NOTE: this
38803   *    is not the case for id ebml tags as there id includes
38804   *    length bits.
38805   *
38806   */
38807
38808
38809  var findEbml = function findEbml(bytes, paths) {
38810    paths = normalizePaths(paths);
38811    bytes = toUint8(bytes);
38812    var results = [];
38813
38814    if (!paths.length) {
38815      return results;
38816    }
38817
38818    var i = 0;
38819
38820    while (i < bytes.length) {
38821      var id = getvint(bytes, i, false);
38822      var dataHeader = getvint(bytes, i + id.length);
38823      var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
38824
38825      if (dataHeader.value === 0x7f) {
38826        dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
38827
38828        if (dataHeader.value !== bytes.length) {
38829          dataHeader.value -= dataStart;
38830        }
38831      }
38832
38833      var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
38834      var data = bytes.subarray(dataStart, dataEnd);
38835
38836      if (bytesMatch(paths[0], id.bytes)) {
38837        if (paths.length === 1) {
38838          // this is the end of the paths and we've found the tag we were
38839          // looking for
38840          results.push(data);
38841        } else {
38842          // recursively search for the next tag inside of the data
38843          // of this one
38844          results = results.concat(findEbml(data, paths.slice(1)));
38845        }
38846      }
38847
38848      var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
38849
38850      i += totalLength;
38851    }
38852
38853    return results;
38854  }; // see https://www.matroska.org/technical/basics.html#block-structure
38855
38856  var NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]);
38857  var NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]);
38858  var EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]);
38859  /**
38860   * Expunge any "Emulation Prevention" bytes from a "Raw Byte
38861   * Sequence Payload"
38862   *
38863   * @param data {Uint8Array} the bytes of a RBSP from a NAL
38864   * unit
38865   * @return {Uint8Array} the RBSP without any Emulation
38866   * Prevention Bytes
38867   */
38868
38869  var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
38870    var positions = [];
38871    var i = 1; // Find all `Emulation Prevention Bytes`
38872
38873    while (i < bytes.length - 2) {
38874      if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
38875        positions.push(i + 2);
38876        i++;
38877      }
38878
38879      i++;
38880    } // If no Emulation Prevention Bytes were found just return the original
38881    // array
38882
38883
38884    if (positions.length === 0) {
38885      return bytes;
38886    } // Create a new array to hold the NAL unit data
38887
38888
38889    var newLength = bytes.length - positions.length;
38890    var newData = new Uint8Array(newLength);
38891    var sourceIndex = 0;
38892
38893    for (i = 0; i < newLength; sourceIndex++, i++) {
38894      if (sourceIndex === positions[0]) {
38895        // Skip this byte
38896        sourceIndex++; // Remove this position index
38897
38898        positions.shift();
38899      }
38900
38901      newData[i] = bytes[sourceIndex];
38902    }
38903
38904    return newData;
38905  };
38906  var findNal = function findNal(bytes, dataType, types, nalLimit) {
38907    if (nalLimit === void 0) {
38908      nalLimit = Infinity;
38909    }
38910
38911    bytes = toUint8(bytes);
38912    types = [].concat(types);
38913    var i = 0;
38914    var nalStart;
38915    var nalsFound = 0; // keep searching until:
38916    // we reach the end of bytes
38917    // we reach the maximum number of nals they want to seach
38918    // NOTE: that we disregard nalLimit when we have found the start
38919    // of the nal we want so that we can find the end of the nal we want.
38920
38921    while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
38922      var nalOffset = void 0;
38923
38924      if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
38925        nalOffset = 4;
38926      } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
38927        nalOffset = 3;
38928      } // we are unsynced,
38929      // find the next nal unit
38930
38931
38932      if (!nalOffset) {
38933        i++;
38934        continue;
38935      }
38936
38937      nalsFound++;
38938
38939      if (nalStart) {
38940        return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
38941      }
38942
38943      var nalType = void 0;
38944
38945      if (dataType === 'h264') {
38946        nalType = bytes[i + nalOffset] & 0x1f;
38947      } else if (dataType === 'h265') {
38948        nalType = bytes[i + nalOffset] >> 1 & 0x3f;
38949      }
38950
38951      if (types.indexOf(nalType) !== -1) {
38952        nalStart = i + nalOffset;
38953      } // nal header is 1 length for h264, and 2 for h265
38954
38955
38956      i += nalOffset + (dataType === 'h264' ? 1 : 2);
38957    }
38958
38959    return bytes.subarray(0, 0);
38960  };
38961  var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
38962    return findNal(bytes, 'h264', type, nalLimit);
38963  };
38964  var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
38965    return findNal(bytes, 'h265', type, nalLimit);
38966  };
38967
38968  var CONSTANTS = {
38969    // "webm" string literal in hex
38970    'webm': toUint8([0x77, 0x65, 0x62, 0x6d]),
38971    // "matroska" string literal in hex
38972    'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
38973    // "fLaC" string literal in hex
38974    'flac': toUint8([0x66, 0x4c, 0x61, 0x43]),
38975    // "OggS" string literal in hex
38976    'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]),
38977    // ac-3 sync byte, also works for ec-3 as that is simply a codec
38978    // of ac-3
38979    'ac3': toUint8([0x0b, 0x77]),
38980    // "RIFF" string literal in hex used for wav and avi
38981    'riff': toUint8([0x52, 0x49, 0x46, 0x46]),
38982    // "AVI" string literal in hex
38983    'avi': toUint8([0x41, 0x56, 0x49]),
38984    // "WAVE" string literal in hex
38985    'wav': toUint8([0x57, 0x41, 0x56, 0x45]),
38986    // "ftyp3g" string literal in hex
38987    '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
38988    // "ftyp" string literal in hex
38989    'mp4': toUint8([0x66, 0x74, 0x79, 0x70]),
38990    // "styp" string literal in hex
38991    'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]),
38992    // "ftypqt" string literal in hex
38993    'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
38994    // moov string literal in hex
38995    'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]),
38996    // moof string literal in hex
38997    'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66])
38998  };
38999  var _isLikely = {
39000    aac: function aac(bytes) {
39001      var offset = getId3Offset(bytes);
39002      return bytesMatch(bytes, [0xFF, 0x10], {
39003        offset: offset,
39004        mask: [0xFF, 0x16]
39005      });
39006    },
39007    mp3: function mp3(bytes) {
39008      var offset = getId3Offset(bytes);
39009      return bytesMatch(bytes, [0xFF, 0x02], {
39010        offset: offset,
39011        mask: [0xFF, 0x06]
39012      });
39013    },
39014    webm: function webm(bytes) {
39015      var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
39016
39017      return bytesMatch(docType, CONSTANTS.webm);
39018    },
39019    mkv: function mkv(bytes) {
39020      var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
39021
39022      return bytesMatch(docType, CONSTANTS.matroska);
39023    },
39024    mp4: function mp4(bytes) {
39025      // if this file is another base media file format, it is not mp4
39026      if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
39027        return false;
39028      } // if this file starts with a ftyp or styp box its mp4
39029
39030
39031      if (bytesMatch(bytes, CONSTANTS.mp4, {
39032        offset: 4
39033      }) || bytesMatch(bytes, CONSTANTS.fmp4, {
39034        offset: 4
39035      })) {
39036        return true;
39037      } // if this file starts with a moof/moov box its mp4
39038
39039
39040      if (bytesMatch(bytes, CONSTANTS.moof, {
39041        offset: 4
39042      }) || bytesMatch(bytes, CONSTANTS.moov, {
39043        offset: 4
39044      })) {
39045        return true;
39046      }
39047    },
39048    mov: function mov(bytes) {
39049      return bytesMatch(bytes, CONSTANTS.mov, {
39050        offset: 4
39051      });
39052    },
39053    '3gp': function gp(bytes) {
39054      return bytesMatch(bytes, CONSTANTS['3gp'], {
39055        offset: 4
39056      });
39057    },
39058    ac3: function ac3(bytes) {
39059      var offset = getId3Offset(bytes);
39060      return bytesMatch(bytes, CONSTANTS.ac3, {
39061        offset: offset
39062      });
39063    },
39064    ts: function ts(bytes) {
39065      if (bytes.length < 189 && bytes.length >= 1) {
39066        return bytes[0] === 0x47;
39067      }
39068
39069      var i = 0; // check the first 376 bytes for two matching sync bytes
39070
39071      while (i + 188 < bytes.length && i < 188) {
39072        if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
39073          return true;
39074        }
39075
39076        i += 1;
39077      }
39078
39079      return false;
39080    },
39081    flac: function flac(bytes) {
39082      var offset = getId3Offset(bytes);
39083      return bytesMatch(bytes, CONSTANTS.flac, {
39084        offset: offset
39085      });
39086    },
39087    ogg: function ogg(bytes) {
39088      return bytesMatch(bytes, CONSTANTS.ogg);
39089    },
39090    avi: function avi(bytes) {
39091      return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, {
39092        offset: 8
39093      });
39094    },
39095    wav: function wav(bytes) {
39096      return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, {
39097        offset: 8
39098      });
39099    },
39100    'h264': function h264(bytes) {
39101      // find seq_parameter_set_rbsp
39102      return findH264Nal(bytes, 7, 3).length;
39103    },
39104    'h265': function h265(bytes) {
39105      // find video_parameter_set_rbsp or seq_parameter_set_rbsp
39106      return findH265Nal(bytes, [32, 33], 3).length;
39107    }
39108  }; // get all the isLikely functions
39109  // but make sure 'ts' is above h264 and h265
39110  // but below everything else as it is the least specific
39111
39112  var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
39113  .filter(function (t) {
39114    return t !== 'ts' && t !== 'h264' && t !== 'h265';
39115  }) // add it back to the bottom
39116  .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
39117
39118  isLikelyTypes.forEach(function (type) {
39119    var isLikelyFn = _isLikely[type];
39120
39121    _isLikely[type] = function (bytes) {
39122      return isLikelyFn(toUint8(bytes));
39123    };
39124  }); // export after wrapping
39125
39126  var isLikely = _isLikely; // A useful list of file signatures can be found here
39127  // https://en.wikipedia.org/wiki/List_of_file_signatures
39128
39129  var detectContainerForBytes = function detectContainerForBytes(bytes) {
39130    bytes = toUint8(bytes);
39131
39132    for (var i = 0; i < isLikelyTypes.length; i++) {
39133      var type = isLikelyTypes[i];
39134
39135      if (isLikely[type](bytes)) {
39136        return type;
39137      }
39138    }
39139
39140    return '';
39141  }; // fmp4 is not a container
39142
39143  var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
39144    return findBox(bytes, ['moof']).length > 0;
39145  };
39146
39147  /**
39148   * mux.js
39149   *
39150   * Copyright (c) Brightcove
39151   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39152   */
39153  var ONE_SECOND_IN_TS = 90000,
39154      // 90kHz clock
39155  secondsToVideoTs,
39156      secondsToAudioTs,
39157      videoTsToSeconds,
39158      audioTsToSeconds,
39159      audioTsToVideoTs,
39160      videoTsToAudioTs,
39161      metadataTsToSeconds;
39162
39163  secondsToVideoTs = function secondsToVideoTs(seconds) {
39164    return seconds * ONE_SECOND_IN_TS;
39165  };
39166
39167  secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
39168    return seconds * sampleRate;
39169  };
39170
39171  videoTsToSeconds = function videoTsToSeconds(timestamp) {
39172    return timestamp / ONE_SECOND_IN_TS;
39173  };
39174
39175  audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
39176    return timestamp / sampleRate;
39177  };
39178
39179  audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
39180    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
39181  };
39182
39183  videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
39184    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
39185  };
39186  /**
39187   * Adjust ID3 tag or caption timing information by the timeline pts values
39188   * (if keepOriginalTimestamps is false) and convert to seconds
39189   */
39190
39191
39192  metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
39193    return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
39194  };
39195
39196  var clock = {
39197    ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
39198    secondsToVideoTs: secondsToVideoTs,
39199    secondsToAudioTs: secondsToAudioTs,
39200    videoTsToSeconds: videoTsToSeconds,
39201    audioTsToSeconds: audioTsToSeconds,
39202    audioTsToVideoTs: audioTsToVideoTs,
39203    videoTsToAudioTs: videoTsToAudioTs,
39204    metadataTsToSeconds: metadataTsToSeconds
39205  };
39206  var clock_1 = clock.ONE_SECOND_IN_TS;
39207
39208  /*! @name @videojs/http-streaming @version 2.10.2 @license Apache-2.0 */
39209  /**
39210   * @file resolve-url.js - Handling how URLs are resolved and manipulated
39211   */
39212
39213  var resolveUrl = resolveUrl$1;
39214  /**
39215   * Checks whether xhr request was redirected and returns correct url depending
39216   * on `handleManifestRedirects` option
39217   *
39218   * @api private
39219   *
39220   * @param  {string} url - an url being requested
39221   * @param  {XMLHttpRequest} req - xhr request result
39222   *
39223   * @return {string}
39224   */
39225
39226  var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
39227    // To understand how the responseURL below is set and generated:
39228    // - https://fetch.spec.whatwg.org/#concept-response-url
39229    // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
39230    if (handleManifestRedirect && req && req.responseURL && url !== req.responseURL) {
39231      return req.responseURL;
39232    }
39233
39234    return url;
39235  };
39236
39237  var logger = function logger(source) {
39238    if (videojs.log.debug) {
39239      return videojs.log.debug.bind(videojs, 'VHS:', source + " >");
39240    }
39241
39242    return function () {};
39243  };
39244  /**
39245   * ranges
39246   *
39247   * Utilities for working with TimeRanges.
39248   *
39249   */
39250
39251
39252  var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
39253  // can be misleading because of precision differences or when the current media has poorly
39254  // aligned audio and video, which can cause values to be slightly off from what you would
39255  // expect. This value is what we consider to be safe to use in such comparisons to account
39256  // for these scenarios.
39257
39258  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
39259
39260  var filterRanges = function filterRanges(timeRanges, predicate) {
39261    var results = [];
39262    var i;
39263
39264    if (timeRanges && timeRanges.length) {
39265      // Search for ranges that match the predicate
39266      for (i = 0; i < timeRanges.length; i++) {
39267        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
39268          results.push([timeRanges.start(i), timeRanges.end(i)]);
39269        }
39270      }
39271    }
39272
39273    return videojs.createTimeRanges(results);
39274  };
39275  /**
39276   * Attempts to find the buffered TimeRange that contains the specified
39277   * time.
39278   *
39279   * @param {TimeRanges} buffered - the TimeRanges object to query
39280   * @param {number} time  - the time to filter on.
39281   * @return {TimeRanges} a new TimeRanges object
39282   */
39283
39284
39285  var findRange = function findRange(buffered, time) {
39286    return filterRanges(buffered, function (start, end) {
39287      return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
39288    });
39289  };
39290  /**
39291   * Returns the TimeRanges that begin later than the specified time.
39292   *
39293   * @param {TimeRanges} timeRanges - the TimeRanges object to query
39294   * @param {number} time - the time to filter on.
39295   * @return {TimeRanges} a new TimeRanges object.
39296   */
39297
39298
39299  var findNextRange = function findNextRange(timeRanges, time) {
39300    return filterRanges(timeRanges, function (start) {
39301      return start - TIME_FUDGE_FACTOR >= time;
39302    });
39303  };
39304  /**
39305   * Returns gaps within a list of TimeRanges
39306   *
39307   * @param {TimeRanges} buffered - the TimeRanges object
39308   * @return {TimeRanges} a TimeRanges object of gaps
39309   */
39310
39311
39312  var findGaps = function findGaps(buffered) {
39313    if (buffered.length < 2) {
39314      return videojs.createTimeRanges();
39315    }
39316
39317    var ranges = [];
39318
39319    for (var i = 1; i < buffered.length; i++) {
39320      var start = buffered.end(i - 1);
39321      var end = buffered.start(i);
39322      ranges.push([start, end]);
39323    }
39324
39325    return videojs.createTimeRanges(ranges);
39326  };
39327  /**
39328   * Calculate the intersection of two TimeRanges
39329   *
39330   * @param {TimeRanges} bufferA
39331   * @param {TimeRanges} bufferB
39332   * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
39333   */
39334
39335
39336  var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
39337    var start = null;
39338    var end = null;
39339    var arity = 0;
39340    var extents = [];
39341    var ranges = [];
39342
39343    if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
39344      return videojs.createTimeRange();
39345    } // Handle the case where we have both buffers and create an
39346    // intersection of the two
39347
39348
39349    var count = bufferA.length; // A) Gather up all start and end times
39350
39351    while (count--) {
39352      extents.push({
39353        time: bufferA.start(count),
39354        type: 'start'
39355      });
39356      extents.push({
39357        time: bufferA.end(count),
39358        type: 'end'
39359      });
39360    }
39361
39362    count = bufferB.length;
39363
39364    while (count--) {
39365      extents.push({
39366        time: bufferB.start(count),
39367        type: 'start'
39368      });
39369      extents.push({
39370        time: bufferB.end(count),
39371        type: 'end'
39372      });
39373    } // B) Sort them by time
39374
39375
39376    extents.sort(function (a, b) {
39377      return a.time - b.time;
39378    }); // C) Go along one by one incrementing arity for start and decrementing
39379    //    arity for ends
39380
39381    for (count = 0; count < extents.length; count++) {
39382      if (extents[count].type === 'start') {
39383        arity++; // D) If arity is ever incremented to 2 we are entering an
39384        //    overlapping range
39385
39386        if (arity === 2) {
39387          start = extents[count].time;
39388        }
39389      } else if (extents[count].type === 'end') {
39390        arity--;
39390 // E) If arity is ever decremented to 1 we leaving an
39391        //    overlapping range
39392
39393        if (arity === 1) {
39394          end = extents[count].time;
39395        }
39396      } // F) Record overlapping ranges
39397
39398
39399      if (start !== null && end !== null) {
39400        ranges.push([start, end]);
39401        start = null;
39402        end = null;
39403      }
39404    }
39405
39406    return videojs.createTimeRanges(ranges);
39407  };
39408  /**
39409   * Gets a human readable string for a TimeRange
39410   *
39411   * @param {TimeRange} range
39412   * @return {string} a human readable string
39413   */
39414
39415
39416  var printableRange = function printableRange(range) {
39417    var strArr = [];
39418
39419    if (!range || !range.length) {
39420      return '';
39421    }
39422
39423    for (var i = 0; i < range.length; i++) {
39424      strArr.push(range.start(i) + ' => ' + range.end(i));
39425    }
39426
39427    return strArr.join(', ');
39428  };
39429  /**
39430   * Calculates the amount of time left in seconds until the player hits the end of the
39431   * buffer and causes a rebuffer
39432   *
39433   * @param {TimeRange} buffered
39434   *        The state of the buffer
39435   * @param {Numnber} currentTime
39436   *        The current time of the player
39437   * @param {number} playbackRate
39438   *        The current playback rate of the player. Defaults to 1.
39439   * @return {number}
39440   *         Time until the player has to start rebuffering in seconds.
39441   * @function timeUntilRebuffer
39442   */
39443
39444
39445  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime, playbackRate) {
39446    if (playbackRate === void 0) {
39447      playbackRate = 1;
39448    }
39449
39450    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
39451    return (bufferedEnd - currentTime) / playbackRate;
39452  };
39453  /**
39454   * Converts a TimeRanges object into an array representation
39455   *
39456   * @param {TimeRanges} timeRanges
39457   * @return {Array}
39458   */
39459
39460
39461  var timeRangesToArray = function timeRangesToArray(timeRanges) {
39462    var timeRangesList = [];
39463
39464    for (var i = 0; i < timeRanges.length; i++) {
39465      timeRangesList.push({
39466        start: timeRanges.start(i),
39467        end: timeRanges.end(i)
39468      });
39469    }
39470
39471    return timeRangesList;
39472  };
39473  /**
39474   * Determines if two time range objects are different.
39475   *
39476   * @param {TimeRange} a
39477   *        the first time range object to check
39478   *
39479   * @param {TimeRange} b
39480   *        the second time range object to check
39481   *
39482   * @return {Boolean}
39483   *         Whether the time range objects differ
39484   */
39485
39486
39487  var isRangeDifferent = function isRangeDifferent(a, b) {
39488    // same object
39489    if (a === b) {
39490      return false;
39491    } // one or the other is undefined
39492
39493
39494    if (!a && b || !b && a) {
39495      return true;
39496    } // length is different
39497
39498
39499    if (a.length !== b.length) {
39500      return true;
39501    } // see if any start/end pair is different
39502
39503
39504    for (var i = 0; i < a.length; i++) {
39505      if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
39506        return true;
39507      }
39508    } // if the length and every pair is the same
39509    // this is the same time range
39510
39511
39512    return false;
39513  };
39514
39515  var lastBufferedEnd = function lastBufferedEnd(a) {
39516    if (!a || !a.length || !a.end) {
39517      return;
39518    }
39519
39520    return a.end(a.length - 1);
39521  };
39522  /**
39523   * @file playlist.js
39524   *
39525   * Playlist related utilities.
39526   */
39527
39528
39529  var createTimeRange = videojs.createTimeRange;
39530  /**
39531   * A function to get a combined list of parts and segments with durations
39532   * and indexes.
39533   *
39534   * @param {Playlist} playlist the playlist to get the list for.
39535   *
39536   * @return {Array} The part/segment list.
39537   */
39538
39539  var getPartsAndSegments = function getPartsAndSegments(playlist) {
39540    return (playlist.segments || []).reduce(function (acc, segment, si) {
39541      if (segment.parts) {
39542        segment.parts.forEach(function (part, pi) {
39543          acc.push({
39544            duration: part.duration,
39545            segmentIndex: si,
39546            partIndex: pi,
39547            part: part,
39548            segment: segment
39549          });
39550        });
39551      } else {
39552        acc.push({
39553          duration: segment.duration,
39554          segmentIndex: si,
39555          partIndex: null,
39556          segment: segment,
39557          part: null
39558        });
39559      }
39560
39561      return acc;
39562    }, []);
39563  };
39564
39565  var getLastParts = function getLastParts(media) {
39566    var lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
39567    return lastSegment && lastSegment.parts || [];
39568  };
39569
39570  var getKnownPartCount = function getKnownPartCount(_ref) {
39571    var preloadSegment = _ref.preloadSegment;
39572
39573    if (!preloadSegment) {
39574      return;
39575    }
39576
39577    var parts = preloadSegment.parts,
39578        preloadHints = preloadSegment.preloadHints;
39579    var partCount = (preloadHints || []).reduce(function (count, hint) {
39580      return count + (hint.type === 'PART' ? 1 : 0);
39581    }, 0);
39582    partCount += parts && parts.length ? parts.length : 0;
39583    return partCount;
39584  };
39585  /**
39586   * Get the number of seconds to delay from the end of a
39587   * live playlist.
39588   *
39589   * @param {Playlist} master the master playlist
39590   * @param {Playlist} media the media playlist
39591   * @return {number} the hold back in seconds.
39592   */
39593
39594
39595  var liveEdgeDelay = function liveEdgeDelay(master, media) {
39596    if (media.endList) {
39597      return 0;
39598    } // dash suggestedPresentationDelay trumps everything
39599
39600
39601    if (master && master.suggestedPresentationDelay) {
39602      return master.suggestedPresentationDelay;
39603    }
39604
39605    var hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
39606
39607    if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
39608      return media.serverControl.partHoldBack;
39609    } else if (hasParts && media.partTargetDuration) {
39610      return media.partTargetDuration * 3; // finally look for full segment delays
39611    } else if (media.serverControl && media.serverControl.holdBack) {
39612      return media.serverControl.holdBack;
39613    } else if (media.targetDuration) {
39614      return media.targetDuration * 3;
39615    }
39616
39617    return 0;
39618  };
39619  /**
39620   * walk backward until we find a duration we can use
39621   * or return a failure
39622   *
39623   * @param {Playlist} playlist the playlist to walk through
39624   * @param {Number} endSequence the mediaSequence to stop walking on
39625   */
39626
39627
39628  var backwardDuration = function backwardDuration(playlist, endSequence) {
39629    var result = 0;
39630    var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
39631    // the interval, use it
39632
39633    var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
39634    // information that is earlier than endSequence
39635
39636    if (segment) {
39637      if (typeof segment.start !== 'undefined') {
39638        return {
39639          result: segment.start,
39640          precise: true
39641        };
39642      }
39643
39644      if (typeof segment.end !== 'undefined') {
39645        return {
39646          result: segment.end - segment.duration,
39647          precise: true
39648        };
39649      }
39650    }
39651
39652    while (i--) {
39653      segment = playlist.segments[i];
39654
39655      if (typeof segment.end !== 'undefined') {
39656        return {
39657          result: result + segment.end,
39658          precise: true
39659        };
39660      }
39661
39662      result += segment.duration;
39663
39664      if (typeof segment.start !== 'undefined') {
39665        return {
39666          result: result + segment.start,
39667          precise: true
39668        };
39669      }
39670    }
39671
39672    return {
39673      result: result,
39674      precise: false
39675    };
39676  };
39677  /**
39678   * walk forward until we find a duration we can use
39679   * or return a failure
39680   *
39681   * @param {Playlist} playlist the playlist to walk through
39682   * @param {number} endSequence the mediaSequence to stop walking on
39683   */
39684
39685
39686  var forwardDuration = function forwardDuration(playlist, endSequence) {
39687    var result = 0;
39688    var segment;
39689    var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
39690    // information
39691
39692    for (; i < playlist.segments.length; i++) {
39693      segment = playlist.segments[i];
39694
39695      if (typeof segment.start !== 'undefined') {
39696        return {
39697          result: segment.start - result,
39698          precise: true
39699        };
39700      }
39701
39702      result += segment.duration;
39703
39704      if (typeof segment.end !== 'undefined') {
39705        return {
39706          result: segment.end - result,
39707          precise: true
39708        };
39709      }
39710    } // indicate we didn't find a useful duration estimate
39711
39712
39713    return {
39714      result: -1,
39715      precise: false
39716    };
39717  };
39718  /**
39719    * Calculate the media duration from the segments associated with a
39720    * playlist. The duration of a subinterval of the available segments
39721    * may be calculated by specifying an end index.
39722    *
39723    * @param {Object} playlist a media playlist object
39724    * @param {number=} endSequence an exclusive upper boundary
39725    * for the playlist.  Defaults to playlist length.
39726    * @param {number} expired the amount of time that has dropped
39727    * off the front of the playlist in a live scenario
39728    * @return {number} the duration between the first available segment
39729    * and end index.
39730    */
39731
39732
39733  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
39734    if (typeof endSequence === 'undefined') {
39735      endSequence = playlist.mediaSequence + playlist.segments.length;
39736    }
39737
39738    if (endSequence < playlist.mediaSequence) {
39739      return 0;
39740    } // do a backward walk to estimate the duration
39741
39742
39743    var backward = backwardDuration(playlist, endSequence);
39744
39745    if (backward.precise) {
39746      // if we were able to base our duration estimate on timing
39747      // information provided directly from the Media Source, return
39748      // it
39749      return backward.result;
39750    } // walk forward to see if a precise duration estimate can be made
39751    // that way
39752
39753
39754    var forward = forwardDuration(playlist, endSequence);
39755
39756    if (forward.precise) {
39757      // we found a segment that has been buffered and so it's
39758      // position is known precisely
39759      return forward.result;
39760    } // return the less-precise, playlist-based duration estimate
39761
39762
39763    return backward.result + expired;
39764  };
39765  /**
39766    * Calculates the duration of a playlist. If a start and end index
39767    * are specified, the duration will be for the subset of the media
39768    * timeline between those two indices. The total duration for live
39769    * playlists is always Infinity.
39770    *
39771    * @param {Object} playlist a media playlist object
39772    * @param {number=} endSequence an exclusive upper
39773    * boundary for the playlist. Defaults to the playlist media
39774    * sequence number plus its length.
39775    * @param {number=} expired the amount of time that has
39776    * dropped off the front of the playlist in a live scenario
39777    * @return {number} the duration between the start index and end
39778    * index.
39779    */
39780
39781
39782  var duration = function duration(playlist, endSequence, expired) {
39783    if (!playlist) {
39784      return 0;
39785    }
39786
39787    if (typeof expired !== 'number') {
39788      expired = 0;
39789    } // if a slice of the total duration is not requested, use
39790    // playlist-level duration indicators when they're present
39791
39792
39793    if (typeof endSequence === 'undefined') {
39794      // if present, use the duration specified in the playlist
39795      if (playlist.totalDuration) {
39796        return playlist.totalDuration;
39797      } // duration should be Infinity for live playlists
39798
39799
39800      if (!playlist.endList) {
39801        return window_1.Infinity;
39802      }
39803    } // calculate the total duration based on the segment durations
39804
39805
39806    return intervalDuration(playlist, endSequence, expired);
39807  };
39808  /**
39809    * Calculate the time between two indexes in the current playlist
39810    * neight the start- nor the end-index need to be within the current
39811    * playlist in which case, the targetDuration of the playlist is used
39812    * to approximate the durations of the segments
39813    *
39814    * @param {Array} options.durationList list to iterate over for durations.
39815    * @param {number} options.defaultDuration duration to use for elements before or after the durationList
39816    * @param {number} options.startIndex partsAndSegments index to start
39817    * @param {number} options.endIndex partsAndSegments index to end.
39818    * @return {number} the number of seconds between startIndex and endIndex
39819    */
39820
39821
39822  var sumDurations = function sumDurations(_ref2) {
39823    var defaultDuration = _ref2.defaultDuration,
39824        durationList = _ref2.durationList,
39825        startIndex = _ref2.startIndex,
39826        endIndex = _ref2.endIndex;
39827    var durations = 0;
39828
39829    if (startIndex > endIndex) {
39830      var _ref3 = [endIndex, startIndex];
39831      startIndex = _ref3[0];
39832      endIndex = _ref3[1];
39833    }
39834
39835    if (startIndex < 0) {
39836      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
39837        durations += defaultDuration;
39838      }
39839
39840      startIndex = 0;
39841    }
39842
39843    for (var _i = startIndex; _i < endIndex; _i++) {
39844      durations += durationList[_i].duration;
39845    }
39846
39847    return durations;
39848  };
39849  /**
39850   * Calculates the playlist end time
39851   *
39852   * @param {Object} playlist a media playlist object
39853   * @param {number=} expired the amount of time that has
39854   *                  dropped off the front of the playlist in a live scenario
39855   * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
39856   *                        playlist end calculation should consider the safe live end
39857   *                        (truncate the playlist end by three segments). This is normally
39858   *                        used for calculating the end of the playlist's seekable range.
39859   *                        This takes into account the value of liveEdgePadding.
39860   *                        Setting liveEdgePadding to 0 is equivalent to setting this to false.
39861   * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
39862   *                 If this is provided, it is used in the safe live end calculation.
39863   *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
39864   *                 Corresponds to suggestedPresentationDelay in DASH manifests.
39865   * @return {number} the end time of playlist
39866   * @function playlistEnd
39867   */
39868
39869
39870  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) {
39871    if (!playlist || !playlist.segments) {
39872      return null;
39873    }
39874
39875    if (playlist.endList) {
39876      return duration(playlist);
39877    }
39878
39879    if (expired === null) {
39880      return null;
39881    }
39882
39883    expired = expired || 0;
39884    var lastSegmentTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
39885
39886    if (useSafeLiveEnd) {
39887      liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
39888      lastSegmentTime -= liveEdgePadding;
39889    } // don't return a time less than zero
39890
39891
39892    return Math.max(0, lastSegmentTime);
39893  };
39894  /**
39895    * Calculates the interval of time that is currently seekable in a
39896    * playlist. The returned time ranges are relative to the earliest
39897    * moment in the specified playlist that is still available. A full
39898    * seekable implementation for live streams would need to offset
39899    * these values by the duration of content that has expired from the
39900    * stream.
39901    *
39902    * @param {Object} playlist a media playlist object
39903    * dropped off the front of the playlist in a live scenario
39904    * @param {number=} expired the amount of time that has
39905    * dropped off the front of the playlist in a live scenario
39906    * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
39907    *        Corresponds to suggestedPresentationDelay in DASH manifests.
39908    * @return {TimeRanges} the periods of time that are valid targets
39909    * for seeking
39910    */
39911
39912
39913  var seekable = function seekable(playlist, expired, liveEdgePadding) {
39914    var useSafeLiveEnd = true;
39915    var seekableStart = expired || 0;
39916    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
39917
39918    if (seekableEnd === null) {
39919      return createTimeRange();
39920    }
39921
39922    return createTimeRange(seekableStart, seekableEnd);
39923  };
39924  /**
39925   * Determine the index and estimated starting time of the segment that
39926   * contains a specified playback position in a media playlist.
39927   *
39928   * @param {Object} options.playlist the media playlist to query
39929   * @param {number} options.currentTime The number of seconds since the earliest
39930   * possible position to determine the containing segment for
39931   * @param {number} options.startTime the time when the segment/part starts
39932   * @param {number} options.startingSegmentIndex the segment index to start looking at.
39933   * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
39934   *
39935   * @return {Object} an object with partIndex, segmentIndex, and startTime.
39936   */
39937
39938
39939  var getMediaInfoForTime = function getMediaInfoForTime(_ref4) {
39940    var playlist = _ref4.playlist,
39941        currentTime = _ref4.currentTime,
39942        startingSegmentIndex = _ref4.startingSegmentIndex,
39943        startingPartIndex = _ref4.startingPartIndex,
39944        startTime = _ref4.startTime,
39945        experimentalExactManifestTimings = _ref4.experimentalExactManifestTimings;
39946    var time = currentTime - startTime;
39947    var partsAndSegments = getPartsAndSegments(playlist);
39948    var startIndex = 0;
39949
39950    for (var i = 0; i < partsAndSegments.length; i++) {
39951      var partAndSegment = partsAndSegments[i];
39952
39953      if (startingSegmentIndex !== partAndSegment.segmentIndex) {
39954        continue;
39955      } // skip this if part index does not match.
39956
39957
39958      if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
39959        continue;
39960      }
39961
39962      startIndex = i;
39963      break;
39964    }
39965
39966    if (time < 0) {
39967      // Walk backward from startIndex in the playlist, adding durations
39968      // until we find a segment that contains `time` and return it
39969      if (startIndex > 0) {
39970        for (var _i2 = startIndex - 1; _i2 >= 0; _i2--) {
39971          var _partAndSegment = partsAndSegments[_i2];
39972          time += _partAndSegment.duration;
39973
39974          if (experimentalExactManifestTimings) {
39975            if (time < 0) {
39976              continue;
39977            }
39978          } else if (time + TIME_FUDGE_FACTOR <= 0) {
39979            continue;
39980          }
39981
39982          return {
39983            partIndex: _partAndSegment.partIndex,
39984            segmentIndex: _partAndSegment.segmentIndex,
39985            startTime: startTime - sumDurations({
39986              defaultDuration: playlist.targetDuration,
39987              durationList: partsAndSegments,
39988              startIndex: startIndex,
39989              endIndex: _i2
39990            })
39991          };
39992        }
39993      } // We were unable to find a good segment within the playlist
39994      // so select the first segment
39995
39996
39997      return {
39998        partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
39999        segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
40000        startTime: currentTime
40001      };
40002    } // When startIndex is negative, we first walk forward to first segment
40003    // adding target durations. If we "run out of time" before getting to
40004    // the first segment, return the first segment
40005
40006
40007    if (startIndex < 0) {
40008      for (var _i3 = startIndex; _i3 < 0; _i3++) {
40009        time -= playlist.targetDuration;
40010
40011        if (time < 0) {
40012          return {
40013            partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
40014            segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
40015            startTime: currentTime
40016          };
40017        }
40018      }
40019
40020      startIndex = 0;
40021    } // Walk forward from startIndex in the playlist, subtracting durations
40022    // until we find a segment that contains `time` and return it
40023
40024
40025    for (var _i4 = startIndex; _i4 < partsAndSegments.length; _i4++) {
40026      var _partAndSegment2 = partsAndSegments[_i4];
40027      time -= _partAndSegment2.duration;
40028
40029      if (experimentalExactManifestTimings) {
40030        if (time > 0) {
40031          continue;
40032        }
40033      } else if (time - TIME_FUDGE_FACTOR >= 0) {
40034        continue;
40035      }
40036
40037      return {
40038        partIndex: _partAndSegment2.partIndex,
40039        segmentIndex: _partAndSegment2.segmentIndex,
40040        startTime: startTime + sumDurations({
40041          defaultDuration: playlist.targetDuration,
40042          durationList: partsAndSegments,
40043          startIndex: startIndex,
40044          endIndex: _i4
40045        })
40046      };
40047    } // We are out of possible candidates so load the last one...
40048
40049
40050    return {
40051      segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
40052      partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
40053      startTime: currentTime
40054    };
40055  };
40056  /**
40057   * Check whether the playlist is blacklisted or not.
40058   *
40059   * @param {Object} playlist the media playlist object
40060   * @return {boolean} whether the playlist is blacklisted or not
40061   * @function isBlacklisted
40062   */
40063
40064
40065  var isBlacklisted = function isBlacklisted(playlist) {
40066    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
40067  };
40068  /**
40069   * Check whether the playlist is compatible with current playback configuration or has
40070   * been blacklisted permanently for being incompatible.
40071   *
40072   * @param {Object} playlist the media playlist object
40073   * @return {boolean} whether the playlist is incompatible or not
40074   * @function isIncompatible
40075   */
40076
40077
40078  var isIncompatible = function isIncompatible(playlist) {
40079    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
40080  };
40081  /**
40082   * Check whether the playlist is enabled or not.
40083   *
40084   * @param {Object} playlist the media playlist object
40085   * @return {boolean} whether the playlist is enabled or not
40086   * @function isEnabled
40087   */
40088
40089
40090  var isEnabled = function isEnabled(playlist) {
40091    var blacklisted = isBlacklisted(playlist);
40092    return !playlist.disabled && !blacklisted;
40093  };
40094  /**
40095   * Check whether the playlist has been manually disabled through the representations api.
40096   *
40097   * @param {Object} playlist the media playlist object
40098   * @return {boolean} whether the playlist is disabled manually or not
40099   * @function isDisabled
40100   */
40101
40102
40103  var isDisabled = function isDisabled(playlist) {
40104    return playlist.disabled;
40105  };
40106  /**
40107   * Returns whether the current playlist is an AES encrypted HLS stream
40108   *
40109   * @return {boolean} true if it's an AES encrypted HLS stream
40110   */
40111
40112
40113  var isAes = function isAes(media) {
40114    for (var i = 0; i < media.segments.length; i++) {
40115      if (media.segments[i].key) {
40116        return true;
40117      }
40118    }
40119
40120    return false;
40121  };
40122  /**
40123   * Checks if the playlist has a value for the specified attribute
40124   *
40125   * @param {string} attr
40126   *        Attribute to check for
40127   * @param {Object} playlist
40128   *        The media playlist object
40129   * @return {boolean}
40130   *         Whether the playlist contains a value for the attribute or not
40131   * @function hasAttribute
40132   */
40133
40134
40135  var hasAttribute = function hasAttribute(attr, playlist) {
40136    return playlist.attributes && playlist.attributes[attr];
40137  };
40138  /**
40139   * Estimates the time required to complete a segment download from the specified playlist
40140   *
40141   * @param {number} segmentDuration
40142   *        Duration of requested segment
40143   * @param {number} bandwidth
40144   *        Current measured bandwidth of the player
40145   * @param {Object} playlist
40146   *        The media playlist object
40147   * @param {number=} bytesReceived
40148   *        Number of bytes already received for the request. Defaults to 0
40149   * @return {number|NaN}
40150   *         The estimated time to request the segment. NaN if bandwidth information for
40151   *         the given playlist is unavailable
40152   * @function estimateSegmentRequestTime
40153   */
40154
40155
40156  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist, bytesReceived) {
40157    if (bytesReceived === void 0) {
40158      bytesReceived = 0;
40159    }
40160
40161    if (!hasAttribute('BANDWIDTH', playlist)) {
40162      return NaN;
40163    }
40164
40165    var size = segmentDuration * playlist.attributes.BANDWIDTH;
40166    return (size - bytesReceived * 8) / bandwidth;
40167  };
40168  /*
40169   * Returns whether the current playlist is the lowest rendition
40170   *
40171   * @return {Boolean} true if on lowest rendition
40172   */
40173
40174
40175  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
40176    if (master.playlists.length === 1) {
40177      return true;
40178    }
40179
40180    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
40181    return master.playlists.filter(function (playlist) {
40182      if (!isEnabled(playlist)) {
40183        return false;
40184      }
40185
40186      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
40187    }).length === 0;
40188  };
40189
40190  var playlistMatch = function playlistMatch(a, b) {
40191    // both playlits are null
40192    // or only one playlist is non-null
40193    // no match
40194    if (!a && !b || !a && b || a && !b) {
40195      return false;
40196    } // playlist objects are the same, match
40197
40198
40199    if (a === b) {
40200      return true;
40201    } // first try to use id as it should be the most
40202    // accurate
40203
40204
40205    if (a.id && b.id && a.id === b.id) {
40206      return true;
40207    } // next try to use reslovedUri as it should be the
40208    // second most accurate.
40209
40210
40211    if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
40212      return true;
40213    } // finally try to use uri as it should be accurate
40214    // but might miss a few cases for relative uris
40215
40216
40217    if (a.uri && b.uri && a.uri === b.uri) {
40218      return true;
40219    }
40220
40221    return false;
40222  };
40223
40224  var someAudioVariant = function someAudioVariant(master, callback) {
40225    var AUDIO = master && master.mediaGroups && master.mediaGroups.AUDIO || {};
40226    var found = false;
40227
40228    for (var groupName in AUDIO) {
40229      for (var label in AUDIO[groupName]) {
40230        found = callback(AUDIO[groupName][label]);
40231
40232        if (found) {
40233          break;
40234        }
40235      }
40236
40237      if (found) {
40238        break;
40239      }
40240    }
40241
40242    return !!found;
40243  };
40244
40245  var isAudioOnly = function isAudioOnly(master) {
40246    // we are audio only if we have no main playlists but do
40247    // have media group playlists.
40248    if (!master || !master.playlists || !master.playlists.length) {
40249      // without audio variants or playlists this
40250      // is not an audio only master.
40251      var found = someAudioVariant(master, function (variant) {
40252        return variant.playlists && variant.playlists.length || variant.uri;
40253      });
40254      return found;
40255    } // if every playlist has only an audio codec it is audio only
40256
40257
40258    var _loop = function _loop(i) {
40259      var playlist = master.playlists[i];
40260      var CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
40261
40262      if (CODECS && CODECS.split(',').every(function (c) {
40263        return isAudioCodec(c);
40264      })) {
40265        return "continue";
40266      } // playlist is in an audio group it is audio only
40267
40268
40269      var found = someAudioVariant(master, function (variant) {
40270        return playlistMatch(playlist, variant);
40271      });
40272
40273      if (found) {
40274        return "continue";
40275      } // if we make it here this playlist isn't audio and we
40276      // are not audio only
40277
40278
40279      return {
40280        v: false
40281      };
40282    };
40283
40284    for (var i = 0; i < master.playlists.length; i++) {
40285      var _ret = _loop(i);
40286
40287      if (_ret === "continue") continue;
40288      if (typeof _ret === "object") return _ret.v;
40289    } // if we make it past every playlist without returning, then
40290    // this is an audio only playlist.
40291
40292
40293    return true;
40294  }; // exports
40295
40296
40297  var Playlist = {
40298    liveEdgeDelay: liveEdgeDelay,
40299    duration: duration,
40300    seekable: seekable,
40301    getMediaInfoForTime: getMediaInfoForTime,
40302    isEnabled: isEnabled,
40303    isDisabled: isDisabled,
40304    isBlacklisted: isBlacklisted,
40305    isIncompatible: isIncompatible,
40306    playlistEnd: playlistEnd,
40307    isAes: isAes,
40308    hasAttribute: hasAttribute,
40309    estimateSegmentRequestTime: estimateSegmentRequestTime,
40310    isLowestEnabledRendition: isLowestEnabledRendition,
40311    isAudioOnly: isAudioOnly,
40312    playlistMatch: playlistMatch
40313  };
40314  var log = videojs.log;
40315
40316  var createPlaylistID = function createPlaylistID(index, uri) {
40317    return index + "-" + uri;
40318  };
40319  /**
40320   * Parses a given m3u8 playlist
40321   *
40322   * @param {Function} [onwarn]
40323   *        a function to call when the parser triggers a warning event.
40324   * @param {Function} [oninfo]
40325   *        a function to call when the parser triggers an info event.
40326   * @param {string} manifestString
40327   *        The downloaded manifest string
40328   * @param {Object[]} [customTagParsers]
40329   *        An array of custom tag parsers for the m3u8-parser instance
40330   * @param {Object[]} [customTagMappers]
40331   *        An array of custom tag mappers for the m3u8-parser instance
40332   * @param {boolean} [experimentalLLHLS=false]
40333   *        Whether to keep ll-hls features in the manifest after parsing.
40334   * @return {Object}
40335   *         The manifest object
40336   */
40337
40338
40339  var parseManifest = function parseManifest(_ref) {
40340    var onwarn = _ref.onwarn,
40341        oninfo = _ref.oninfo,
40342        manifestString = _ref.manifestString,
40343        _ref$customTagParsers = _ref.customTagParsers,
40344        customTagParsers = _ref$customTagParsers === void 0 ? [] : _ref$customTagParsers,
40345        _ref$customTagMappers = _ref.customTagMappers,
40346        customTagMappers = _ref$customTagMappers === void 0 ? [] : _ref$customTagMappers,
40347        experimentalLLHLS = _ref.experimentalLLHLS;
40348    var parser = new Parser();
40349
40350    if (onwarn) {
40351      parser.on('warn', onwarn);
40352    }
40353
40354    if (oninfo) {
40355      parser.on('info', oninfo);
40356    }
40357
40358    customTagParsers.forEach(function (customParser) {
40359      return parser.addParser(customParser);
40360    });
40361    customTagMappers.forEach(function (mapper) {
40362      return parser.addTagMapper(mapper);
40363    });
40364    parser.push(manifestString);
40365    parser.end();
40366    var manifest = parser.manifest; // remove llhls features from the parsed manifest
40367    // if we don't want llhls support.
40368
40369    if (!experimentalLLHLS) {
40370      ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
40371        if (manifest.hasOwnProperty(k)) {
40372          delete manifest[k];
40373        }
40374      });
40375
40376      if (manifest.segments) {
40377        manifest.segments.forEach(function (segment) {
40378          ['parts', 'preloadHints'].forEach(function (k) {
40379            if (segment.hasOwnProperty(k)) {
40380              delete segment[k];
40381            }
40382          });
40383        });
40384      }
40385    }
40386
40387    if (!manifest.targetDuration) {
40388      var targetDuration = 10;
40389
40390      if (manifest.segments && manifest.segments.length) {
40391        targetDuration = manifest.segments.reduce(function (acc, s) {
40392          return Math.max(acc, s.duration);
40393        }, 0);
40394      }
40395
40396      if (onwarn) {
40397        onwarn("manifest has no targetDuration defaulting to " + targetDuration);
40398      }
40399
40400      manifest.targetDuration = targetDuration;
40401    }
40402
40403    var parts = getLastParts(manifest);
40404
40405    if (parts.length && !manifest.partTargetDuration) {
40406      var partTargetDuration = parts.reduce(function (acc, p) {
40407        return Math.max(acc, p.duration);
40408      }, 0);
40409
40410      if (onwarn) {
40411        onwarn("manifest has no partTargetDuration defaulting to " + partTargetDuration);
40412        log.error('LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.');
40413      }
40414
40415      manifest.partTargetDuration = partTargetDuration;
40416    }
40417
40418    return manifest;
40419  };
40420  /**
40421   * Loops through all supported media groups in master and calls the provided
40422   * callback for each group
40423   *
40424   * @param {Object} master
40425   *        The parsed master manifest object
40426   * @param {Function} callback
40427   *        Callback to call for each media group
40428   */
40429
40430
40431  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
40432    if (!master.mediaGroups) {
40433      return;
40434    }
40435
40436    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
40437      if (!master.mediaGroups[mediaType]) {
40438        return;
40439      }
40440
40441      for (var groupKey in master.mediaGroups[mediaType]) {
40442        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
40443          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
40444          callback(mediaProperties, mediaType, groupKey, labelKey);
40445        }
40446      }
40447    });
40448  };
40449  /**
40450   * Adds properties and attributes to the playlist to keep consistent functionality for
40451   * playlists throughout VHS.
40452   *
40453   * @param {Object} config
40454   *        Arguments object
40455   * @param {Object} config.playlist
40456   *        The media playlist
40457   * @param {string} [config.uri]
40458   *        The uri to the media playlist (if media playlist is not from within a master
40459   *        playlist)
40460   * @param {string} id
40461   *        ID to use for the playlist
40462   */
40463
40464
40465  var setupMediaPlaylist = function setupMediaPlaylist(_ref2) {
40466    var playlist = _ref2.playlist,
40467        uri = _ref2.uri,
40468        id = _ref2.id;
40469    playlist.id = id;
40470    playlist.playlistErrors_ = 0;
40471
40472    if (uri) {
40473      // For media playlists, m3u8-parser does not have access to a URI, as HLS media
40474      // playlists do not contain their own source URI, but one is needed for c
40474onsistency in
40475      // VHS.
40476      playlist.uri = uri;
40477    } // For HLS master playlists, even though certain attributes MUST be defined, the
40478    // stream may still be played without them.
40479    // For HLS media playlists, m3u8-parser does not attach an attributes object to the
40480    // manifest.
40481    //
40482    // To avoid undefined reference errors through the project, and make the code easier
40483    // to write/read, add an empty attributes object for these cases.
40484
40485
40486    playlist.attributes = playlist.attributes || {};
40487  };
40488  /**
40489   * Adds ID, resolvedUri, and attributes properties to each playlist of the master, where
40490   * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
40491   * playlist references to the playlists array.
40492   *
40493   * @param {Object} master
40494   *        The master playlist
40495   */
40496
40497
40498  var setupMediaPlaylists = function setupMediaPlaylists(master) {
40499    var i = master.playlists.length;
40500
40501    while (i--) {
40502      var playlist = master.playlists[i];
40503      setupMediaPlaylist({
40504        playlist: playlist,
40505        id: createPlaylistID(i, playlist.uri)
40506      });
40507      playlist.resolvedUri = resolveUrl(master.uri, playlist.uri);
40508      master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
40509
40510      master.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
40511      // the stream can be played without it. Although an attributes property may have been
40512      // added to the playlist to prevent undefined references, issue a warning to fix the
40513      // manifest.
40514
40515      if (!playlist.attributes.BANDWIDTH) {
40516        log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
40517      }
40518    }
40519  };
40520  /**
40521   * Adds resolvedUri properties to each media group.
40522   *
40523   * @param {Object} master
40524   *        The master playlist
40525   */
40526
40527
40528  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
40529    forEachMediaGroup(master, function (properties) {
40530      if (properties.uri) {
40531        properties.resolvedUri = resolveUrl(master.uri, properties.uri);
40532      }
40533    });
40534  };
40535  /**
40536   * Creates a master playlist wrapper to insert a sole media playlist into.
40537   *
40538   * @param {Object} media
40539   *        Media playlist
40540   * @param {string} uri
40541   *        The media URI
40542   *
40543   * @return {Object}
40544   *         Master playlist
40545   */
40546
40547
40548  var masterForMedia = function masterForMedia(media, uri) {
40549    var id = createPlaylistID(0, uri);
40550    var master = {
40551      mediaGroups: {
40552        'AUDIO': {},
40553        'VIDEO': {},
40554        'CLOSED-CAPTIONS': {},
40555        'SUBTITLES': {}
40556      },
40557      uri: window_1.location.href,
40558      resolvedUri: window_1.location.href,
40559      playlists: [{
40560        uri: uri,
40561        id: id,
40562        resolvedUri: uri,
40563        // m3u8-parser does not attach an attributes property to media playlists so make
40564        // sure that the property is attached to avoid undefined reference errors
40565        attributes: {}
40566      }]
40567    }; // set up ID reference
40568
40569    master.playlists[id] = master.playlists[0]; // URI reference added for backwards compatibility
40570
40571    master.playlists[uri] = master.playlists[0];
40572    return master;
40573  };
40574  /**
40575   * Does an in-place update of the master manifest to add updated playlist URI references
40576   * as well as other properties needed by VHS that aren't included by the parser.
40577   *
40578   * @param {Object} master
40579   *        Master manifest object
40580   * @param {string} uri
40581   *        The source URI
40582   */
40583
40584
40585  var addPropertiesToMaster = function addPropertiesToMaster(master, uri) {
40586    master.uri = uri;
40587
40588    for (var i = 0; i < master.playlists.length; i++) {
40589      if (!master.playlists[i].uri) {
40590        // Set up phony URIs for the playlists since playlists are referenced by their URIs
40591        // throughout VHS, but some formats (e.g., DASH) don't have external URIs
40592        // TODO: consider adding dummy URIs in mpd-parser
40593        var phonyUri = "placeholder-uri-" + i;
40594        master.playlists[i].uri = phonyUri;
40595      }
40596    }
40597
40598    var audioOnlyMaster = isAudioOnly(master);
40599    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
40600      var groupId = "placeholder-uri-" + mediaType + "-" + groupKey + "-" + labelKey; // add a playlist array under properties
40601
40602      if (!properties.playlists || !properties.playlists.length) {
40603        // If the manifest is audio only and this media group does not have a uri, check
40604        // if the media group is located in the main list of playlists. If it is, don't add
40605        // placeholder properties as it shouldn't be considered an alternate audio track.
40606        if (audioOnlyMaster && mediaType === 'AUDIO' && !properties.uri) {
40607          for (var _i = 0; _i < master.playlists.length; _i++) {
40608            var p = master.playlists[_i];
40609
40610            if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
40611              return;
40612            }
40613          }
40614        }
40615
40616        properties.playlists = [_extends_1({}, properties)];
40617      }
40618
40619      properties.playlists.forEach(function (p, i) {
40620        var id = createPlaylistID(i, groupId);
40621
40622        if (p.uri) {
40623          p.resolvedUri = p.resolvedUri || resolveUrl(master.uri, p.uri);
40624        } else {
40625          // DEPRECATED, this has been added to prevent a breaking change.
40626          // previously we only ever had a single media group playlist, so
40627          // we mark the first playlist uri without prepending the index as we used to
40628          // ideally we would do all of the playlists the same way.
40629          p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
40630          // the placeholder again
40631
40632          p.resolvedUri = p.uri;
40633        }
40634
40635        p.id = p.id || id; // add an empty attributes object, all playlists are
40636        // expected to have this.
40637
40638        p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
40639
40640        master.playlists[p.id] = p;
40641        master.playlists[p.uri] = p;
40642      });
40643    });
40644    setupMediaPlaylists(master);
40645    resolveMediaGroupUris(master);
40646  };
40647
40648  var mergeOptions$2 = videojs.mergeOptions,
40649      EventTarget$1 = videojs.EventTarget;
40650
40651  var addLLHLSQueryDirectives = function addLLHLSQueryDirectives(uri, media) {
40652    if (media.endList) {
40653      return uri;
40654    }
40655
40656    var query = [];
40657
40658    if (media.serverControl && media.serverControl.canBlockReload) {
40659      var preloadSegment = media.preloadSegment; // next msn is a zero based value, length is not.
40660
40661      var nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
40662      // that we are going to request a part of that preload segment.
40663      // the logic below is used to determine that.
40664
40665      if (preloadSegment) {
40666        var parts = preloadSegment.parts || []; // _HLS_part is a zero based index
40667
40668        var nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
40669        // length of parts, then we know we had part preload hints
40670        // and we need to add the _HLS_part= query
40671
40672        if (nextPart > -1 && nextPart !== parts.length - 1) {
40673          // add existing parts to our preload hints
40674          query.push("_HLS_part=" + nextPart);
40675        } // this if statement makes sure that we request the msn
40676        // of the preload segment if:
40677        // 1. the preload segment had parts (and was not yet a full segment)
40678        //    but was added to our segments array
40679        // 2. the preload segment had preload hints for parts that are not in
40680        //    the manifest yet.
40681        // in all other cases we want the segment after the preload segment
40682        // which will be given by using media.segments.length because it is 1 based
40683        // rather than 0 based.
40684
40685
40686        if (nextPart > -1 || parts.length) {
40687          nextMSN--;
40688        }
40689      } // add _HLS_msn= in front of any _HLS_part query
40690
40691
40692      query.unshift("_HLS_msn=" + nextMSN);
40693    }
40694
40695    if (media.serverControl && media.serverControl.canSkipUntil) {
40696      // add _HLS_skip= infront of all other queries.
40697      query.unshift('_HLS_skip=' + (media.serverControl.canSkipDateranges ? 'v2' : 'YES'));
40698    }
40699
40700    query.forEach(function (str, i) {
40701      var symbol = i === 0 ? '?' : '&';
40702      uri += "" + symbol + str;
40703    });
40704    return uri;
40705  };
40706  /**
40707   * Returns a new segment object with properties and
40708   * the parts array merged.
40709   *
40710   * @param {Object} a the old segment
40711   * @param {Object} b the new segment
40712   *
40713   * @return {Object} the merged segment
40714   */
40715
40716
40717  var updateSegment = function updateSegment(a, b) {
40718    if (!a) {
40719      return b;
40720    }
40721
40722    var result = mergeOptions$2(a, b); // if only the old segment has preload hints
40723    // and the new one does not, remove preload hints.
40724
40725    if (a.preloadHints && !b.preloadHints) {
40726      delete result.preloadHints;
40727    } // if only the old segment has parts
40728    // then the parts are no longer valid
40729
40730
40731    if (a.parts && !b.parts) {
40732      delete result.parts; // if both segments have parts
40733      // copy part propeties from the old segment
40734      // to the new one.
40735    } else if (a.parts && b.parts) {
40736      for (var i = 0; i < b.parts.length; i++) {
40737        if (a.parts && a.parts[i]) {
40738          result.parts[i] = mergeOptions$2(a.parts[i], b.parts[i]);
40739        }
40740      }
40741    } // set skipped to false for segments that have
40742    // have had information merged from the old segment.
40743
40744
40745    if (!a.skipped && b.skipped) {
40746      result.skipped = false;
40747    } // set preload to false for segments that have
40748    // had information added in the new segment.
40749
40750
40751    if (a.preload && !b.preload) {
40752      result.preload = false;
40753    }
40754
40755    return result;
40756  };
40757  /**
40758   * Returns a new array of segments that is the result of merging
40759   * properties from an older list of segments onto an updated
40760   * list. No properties on the updated playlist will be ovewritten.
40761   *
40762   * @param {Array} original the outdated list of segments
40763   * @param {Array} update the updated list of segments
40764   * @param {number=} offset the index of the first update
40765   * segment in the original segment list. For non-live playlists,
40766   * this should always be zero and does not need to be
40767   * specified. For live playlists, it should be the difference
40768   * between the media sequence numbers in the original and updated
40769   * playlists.
40770   * @return {Array} a list of merged segment objects
40771   */
40772
40773
40774  var updateSegments = function updateSegments(original, update, offset) {
40775    var oldSegments = original.slice();
40776    var newSegments = update.slice();
40777    offset = offset || 0;
40778    var result = [];
40779    var currentMap;
40780
40781    for (var newIndex = 0; newIndex < newSegments.length; newIndex++) {
40782      var oldSegment = oldSegments[newIndex + offset];
40783      var newSegment = newSegments[newIndex];
40784
40785      if (oldSegment) {
40786        currentMap = oldSegment.map || currentMap;
40787        result.push(updateSegment(oldSegment, newSegment));
40788      } else {
40789        // carry over map to new segment if it is missing
40790        if (currentMap && !newSegment.map) {
40791          newSegment.map = currentMap;
40792        }
40793
40794        result.push(newSegment);
40795      }
40796    }
40797
40798    return result;
40799  };
40800
40801  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
40802    // preloadSegment will not have a uri at all
40803    // as the segment isn't actually in the manifest yet, only parts
40804    if (!segment.resolvedUri && segment.uri) {
40805      segment.resolvedUri = resolveUrl(baseUri, segment.uri);
40806    }
40807
40808    if (segment.key && !segment.key.resolvedUri) {
40809      segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
40810    }
40811
40812    if (segment.map && !segment.map.resolvedUri) {
40813      segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
40814    }
40815
40816    if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
40817      segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
40818    }
40819
40820    if (segment.parts && segment.parts.length) {
40821      segment.parts.forEach(function (p) {
40822        if (p.resolvedUri) {
40823          return;
40824        }
40825
40826        p.resolvedUri = resolveUrl(baseUri, p.uri);
40827      });
40828    }
40829
40830    if (segment.preloadHints && segment.preloadHints.length) {
40831      segment.preloadHints.forEach(function (p) {
40832        if (p.resolvedUri) {
40833          return;
40834        }
40835
40836        p.resolvedUri = resolveUrl(baseUri, p.uri);
40837      });
40838    }
40839  };
40840
40841  var getAllSegments = function getAllSegments(media) {
40842    var segments = media.segments || [];
40843    var preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
40844    // a usable segment, only include a preloadSegment that has
40845    // parts.
40846
40847    if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
40848      // if preloadHints has a MAP that means that the
40849      // init segment is going to change. We cannot use any of the parts
40850      // from this preload segment.
40851      if (preloadSegment.preloadHints) {
40852        for (var i = 0; i < preloadSegment.preloadHints.length; i++) {
40853          if (preloadSegment.preloadHints[i].type === 'MAP') {
40854            return segments;
40855          }
40856        }
40857      } // set the duration for our preload segment to target duration.
40858
40859
40860      preloadSegment.duration = media.targetDuration;
40861      preloadSegment.preload = true;
40862      segments.push(preloadSegment);
40863    }
40864
40865    return segments;
40866  }; // consider the playlist unchanged if the playlist object is the same or
40867  // the number of segments is equal, the media sequence number is unchanged,
40868  // and this playlist hasn't become the end of the playlist
40869
40870
40871  var isPlaylistUnchanged = function isPlaylistUnchanged(a, b) {
40872    return a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence;
40873  };
40874  /**
40875    * Returns a new master playlist that is the result of merging an
40876    * updated media playlist into the original version. If the
40877    * updated media playlist does not match any of the playlist
40878    * entries in the original master playlist, null is returned.
40879    *
40880    * @param {Object} master a parsed master M3U8 object
40881    * @param {Object} media a parsed media M3U8 object
40882    * @return {Object} a new object that represents the original
40883    * master playlist with the updated media playlist merged in, or
40884    * null if the merge produced no change.
40885    */
40886
40887
40888  var updateMaster$1 = function updateMaster(master, newMedia, unchangedCheck) {
40889    if (unchangedCheck === void 0) {
40890      unchangedCheck = isPlaylistUnchanged;
40891    }
40892
40893    var result = mergeOptions$2(master, {});
40894    var oldMedia = result.playlists[newMedia.id];
40895
40896    if (!oldMedia) {
40897      return null;
40898    }
40899
40900    if (unchangedCheck(oldMedia, newMedia)) {
40901      return null;
40902    }
40903
40904    newMedia.segments = getAllSegments(newMedia);
40905    var mergedPlaylist = mergeOptions$2(oldMedia, newMedia); // always use the new media's preload segment
40906
40907    if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
40908      delete mergedPlaylist.preloadSegment;
40909    } // if the update could overlap existing segment information, merge the two segment lists
40910
40911
40912    if (oldMedia.segments) {
40913      if (newMedia.skip) {
40914        newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
40915        // old properties into the new segments
40916
40917        for (var i = 0; i < newMedia.skip.skippedSegments; i++) {
40918          newMedia.segments.unshift({
40919            skipped: true
40920          });
40921        }
40922      }
40923
40924      mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
40925    } // resolve any segment URIs to prevent us from having to do it later
40926
40927
40928    mergedPlaylist.segments.forEach(function (segment) {
40929      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
40930    }); // TODO Right now in the playlists array there are two references to each playlist, one
40931    // that is referenced by index, and one by URI. The index reference may no longer be
40932    // necessary.
40933
40934    for (var _i = 0; _i < result.playlists.length; _i++) {
40935      if (result.playlists[_i].id === newMedia.id) {
40936        result.playlists[_i] = mergedPlaylist;
40937      }
40938    }
40939
40940    result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
40941
40942    result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
40943
40944    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
40945      if (!properties.playlists) {
40946        return;
40947      }
40948
40949      for (var _i2 = 0; _i2 < properties.playlists.length; _i2++) {
40950        if (newMedia.id === properties.playlists[_i2].id) {
40951          properties.playlists[_i2] = newMedia;
40952        }
40953      }
40954    });
40955    return result;
40956  };
40957  /**
40958   * Calculates the time to wait before refreshing a live playlist
40959   *
40960   * @param {Object} media
40961   *        The current media
40962   * @param {boolean} update
40963   *        True if there were any updates from the last refresh, false otherwise
40964   * @return {number}
40965   *         The time in ms to wait before refreshing the live playlist
40966   */
40967
40968
40969  var refreshDelay = function refreshDelay(media, update) {
40970    var lastSegment = media.segments[media.segments.length - 1];
40971    var lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
40972    var lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
40973
40974    if (update && lastDuration) {
40975      return lastDuration * 1000;
40976    } // if the playlist is unchanged since the last reload or last segment duration
40977    // cannot be determined, try again after half the target duration
40978
40979
40980    return (media.partTargetDuration || media.targetDuration || 10) * 500;
40981  };
40982  /**
40983   * Load a playlist from a remote location
40984   *
40985   * @class PlaylistLoader
40986   * @extends Stream
40987   * @param {string|Object} src url or object of manifest
40988   * @param {boolean} withCredentials the withCredentials xhr option
40989   * @class
40990   */
40991
40992
40993  var PlaylistLoader = /*#__PURE__*/function (_EventTarget) {
40994    inheritsLoose(PlaylistLoader, _EventTarget);
40995
40996    function PlaylistLoader(src, vhs, options) {
40997      var _this;
40998
40999      if (options === void 0) {
41000        options = {};
41001      }
41002
41003      _this = _EventTarget.call(this) || this;
41004
41005      if (!src) {
41006        throw new Error('A non-empty playlist URL or object is required');
41007      }
41008
41009      _this.logger_ = logger('PlaylistLoader');
41010      var _options = options,
41011          _options$withCredenti = _options.withCredentials,
41012          withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
41013          _options$handleManife = _options.handleManifestRedirects,
41014          handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
41015      _this.src = src;
41016      _this.vhs_ = vhs;
41017      _this.withCredentials = withCredentials;
41018      _this.handleManifestRedirects = handleManifestRedirects;
41019      var vhsOptions = vhs.options_;
41020      _this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
41021      _this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
41022      _this.experimentalLLHLS = vhsOptions && vhsOptions.experimentalLLHLS || false; // initialize the loader state
41023
41024      _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
41025
41026      _this.handleMediaupdatetimeout_ = _this.handleMediaupdatetimeout_.bind(assertThisInitialized(_this));
41027
41028      _this.on('mediaupdatetimeout', _this.handleMediaupdatetimeout_);
41029
41030      return _this;
41031    }
41032
41033    var _proto = PlaylistLoader.prototype;
41034
41035    _proto.handleMediaupdatetimeout_ = function handleMediaupdatetimeout_() {
41036      var _this2 = this;
41037
41038      if (this.state !== 'HAVE_METADATA') {
41039        // only refresh the media playlist if no other activity is going on
41040        return;
41041      }
41042
41043      var media = this.media();
41044      var uri = resolveUrl(this.master.uri, media.uri);
41045
41046      if (this.experimentalLLHLS) {
41047        uri = addLLHLSQueryDirectives(uri, media);
41048      }
41049
41050      this.state = 'HAVE_CURRENT_METADATA';
41051      this.request = this.vhs_.xhr({
41052        uri: uri,
41053        withCredentials: this.withCredentials
41054      }, function (error, req) {
41055        // disposed
41056        if (!_this2.request) {
41057          return;
41058        }
41059
41060        if (error) {
41061          return _this2.playlistRequestError(_this2.request, _this2.media(), 'HAVE_METADATA');
41062        }
41063
41064        _this2.haveMetadata({
41065          playlistString: _this2.request.responseText,
41066          url: _this2.media().uri,
41067          id: _this2.media().id
41068        });
41069      });
41070    };
41071
41072    _proto.playlistRequestError = function playlistRequestError(xhr, playlist, startingState) {
41073      var uri = playlist.uri,
41074          id = playlist.id; // any in-flight request is now finished
41075
41076      this.request = null;
41077
41078      if (startingState) {
41079        this.state = startingState;
41080      }
41081
41082      this.error = {
41083        playlist: this.master.playlists[id],
41084        status: xhr.status,
41085        message: "HLS playlist request error at URL: " + uri + ".",
41086        responseText: xhr.responseText,
41087        code: xhr.status >= 500 ? 4 : 2
41088      };
41089      this.trigger('error');
41090    };
41091
41092    _proto.parseManifest_ = function parseManifest_(_ref) {
41093      var _this3 = this;
41094
41095      var url = _ref.url,
41096          manifestString = _ref.manifestString;
41097      return parseManifest({
41098        onwarn: function onwarn(_ref2) {
41099          var message = _ref2.message;
41100          return _this3.logger_("m3u8-parser warn for " + url + ": " + message);
41101        },
41102        oninfo: function oninfo(_ref3) {
41103          var message = _ref3.message;
41104          return _this3.logger_("m3u8-parser info for " + url + ": " + message);
41105        },
41106        manifestString: manifestString,
41107        customTagParsers: this.customTagParsers,
41108        customTagMappers: this.customTagMappers,
41109        experimentalLLHLS: this.experimentalLLHLS
41110      });
41111    }
41112    /**
41113     * Update the playlist loader's state in response to a new or updated playlist.
41114     *
41115     * @param {string} [playlistString]
41116     *        Playlist string (if playlistObject is not provided)
41117     * @param {Object} [playlistObject]
41118     *        Playlist object (if playlistString is not provided)
41119     * @param {string} url
41120     *        URL of playlist
41121     * @param {string} id
41122     *        ID to use for playlist
41123     */
41124    ;
41125
41126    _proto.haveMetadata = function haveMetadata(_ref4) {
41127      var playlistString = _ref4.playlistString,
41128          playlistObject = _ref4.playlistObject,
41129          url = _ref4.url,
41130          id = _ref4.id; // any in-flight request is now finished
41131
41132      this.request = null;
41133      this.state = 'HAVE_METADATA';
41134      var playlist = playlistObject || this.parseManifest_({
41135        url: url,
41136        manifestString: playlistString
41137      });
41138      playlist.lastRequest = Date.now();
41139      setupMediaPlaylist({
41140        playlist: playlist,
41141        uri: url,
41142        id: id
41143      }); // merge this playlist into the master
41144
41145      var update = updateMaster$1(this.master, playlist);
41146      this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
41147
41148      if (update) {
41149        this.master = update;
41150        this.media_ = this.master.playlists[id];
41151      } else {
41152        this.trigger('playlistunchanged');
41153      }
41154
41155      this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
41156      this.trigger('loadedplaylist');
41157    }
41158    /**
41159      * Abort any outstanding work and clean up.
41160      */
41161    ;
41162
41163    _proto.dispose = function dispose() {
41164      this.trigger('dispose');
41165      this.stopRequest();
41166      window_1.clearTimeout(this.mediaUpdateTimeout);
41167      window_1.clearTimeout(this.finalRenditionTimeout);
41168      this.off();
41169    };
41170
41171    _proto.stopRequest = function stopRequest() {
41172      if (this.request) {
41173        var oldRequest = this.request;
41174        this.request = null;
41175        oldRequest.onreadystatechange = null;
41176        oldRequest.abort();
41177      }
41178    }
41179    /**
41180      * When called without any arguments, returns the currently
41181      * active media playlist. When called with a single argument,
41182      * triggers the playlist loader to asynchronously switch to the
41183      * specified media playlist. Calling this method while the
41184      * loader is in the HAVE_NOTHING causes an error to be emitted
41185      * but otherwise has no effect.
41186      *
41187      * @param {Object=} playlist the parsed media playlist
41188      * object to switch to
41189      * @param {boolean=} shouldDelay whether we should delay the request by half target duration
41190      *
41191      * @return {Playlist} the current loaded media
41192      */
41193    ;
41194
41195    _proto.media = function media(playlist, shouldDelay) {
41196      var _this4 = this; // getter
41197
41198
41199      if (!playlist) {
41200        return this.media_;
41201      } // setter
41202
41203
41204      if (this.state === 'HAVE_NOTHING') {
41205        throw new Error('Cannot switch media playlist from ' + this.state);
41206      } // find the playlist object if the target playlist has been
41207      // specified by URI
41208
41209
41210      if (typeof playlist === 'string') {
41211        if (!this.master.playlists[playlist]) {
41212          throw new Error('Unknown playlist URI: ' + playlist);
41213        }
41214
41215        playlist = this.master.playlists[playlist];
41216      }
41217
41218      window_1.clearTimeout(this.finalRenditionTimeout);
41219
41220      if (shouldDelay) {
41221        var delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
41222        this.finalRenditionTimeout = window_1.setTimeout(this.media.bind(this, playlist, false), delay);
41223        return;
41224      }
41225
41226      var startingState = this.state;
41227      var mediaChange = !this.media_ || playlist.id !== this.media_.id;
41228      var masterPlaylistRef = this.master.playlists[playlist.id]; // switch to fully loaded playlists immediately
41229
41230      if (masterPlaylistRef && masterPlaylistRef.endList || // handle the case of a playlist object (e.g., if using vhs-json with a resolved
41231      // media playlist or, for the case of demuxed audio, a resolved audio media group)
41232      playlist.endList && playlist.segments.length) {
41233        // abort outstanding playlist requests
41234        if (this.request) {
41235          this.request.onreadystatechange = null;
41236          this.request.abort();
41237          this.request = null;
41238        }
41239
41240        this.state = 'HAVE_METADATA';
41241        this.media_ = playlist; // trigger media change if the active media has been updated
41242
41243        if (mediaChange) {
41244          this.trigger('mediachanging');
41245
41246          if (startingState === 'HAVE_MASTER') {
41247            // The initial playlist was a master manifest, and the first media selected was
41248            // also provided (in the form of a resolved playlist object) as part of the
41249            // source object (rather than just a URL). Therefore, since the media playlist
41250            // doesn't need to be requested, loadedmetadata won't trigger as part of the
41251            // normal flow, and needs an explicit trigger here.
41252            this.trigger('loadedmetadata');
41253          } else {
41254            this.trigger('mediachange');
41255          }
41256        }
41257
41258        return;
41259      } // We update/set the timeout here so that live playlists
41260      // that are not a media change will "start" the loader as expected.
41261      // We expect that this function will start the media update timeout
41262      // cycle again. This also prevents a playlist switch failure from
41263      // causing us to stall during live.
41264
41265
41266      this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
41267
41268      if (!mediaChange) {
41269        return;
41270      }
41271
41272      this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
41273
41274      if (this.request) {
41275        if (playlist.resolvedUri === this.request.url) {
41276          // requesting to switch to the same playlist multiple times
41277          // has no effect after the first
41278          return;
41279        }
41280
41281        this.request.onreadystatechange = null;
41282        this.request.abort();
41283        this.request = null;
41284      } // request the new playlist
41285
41286
41287      if (this.media_) {
41288        this.trigger('mediachanging');
41289      }
41290
41291      this.request = this.vhs_.xhr({
41292        uri: playlist.resolvedUri,
41293        withCredentials: this.withCredentials
41294      }, function (error, req) {
41295        // disposed
41296        if (!_this4.request) {
41297          return;
41298        }
41299
41300        playlist.lastRequest = Date.now();
41301        playlist.resolvedUri = resolveManifestRedirect(_this4.handleManifestRedirects, playlist.resolvedUri, req);
41302
41303        if (error) {
41304          return _this4.playlistRequestError(_this4.request, playlist, startingState);
41305        }
41306
41307        _this4.haveMetadata({
41308          playlistString: req.responseText,
41309          url: playlist.uri,
41310          id: playlist.id
41311        }); // fire loadedmetadata the first time a media playlist is loaded
41312
41313
41314        if (startingState === 'HAVE_MASTER') {
41315          _this4.trigger('loadedmetadata');
41316        } else {
41317          _this4.trigger('mediachange');
41318        }
41319      });
41320    }
41321    /**
41322     * pause loading of the playlist
41323     */
41324    ;
41325
41326    _proto.pause = function pause() {
41327      if (this.mediaUpdateTimeout) {
41328        window_1.clearTimeout(this.mediaUpdateTimeout);
41329        this.mediaUpdateTimeout = null;
41330      }
41331
41332      this.stopRequest();
41333
41334      if (this.state === 'HAVE_NOTHING') {
41335        // If we pause the loader before any data has been retrieved, its as if we never
41336        // started, so reset to an unstarted state.
41337        this.started = false;
41338      } // Need to restore state now that no activity is happening
41339
41340
41341      if (this.state === 'SWITCHING_MEDIA') {
41342        // if the loader was in the process of switching media, it should either return to
41343        // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
41344        // playlist yet. This is determined by the existence of loader.media_
41345        if (this.media_) {
41346          this.state = 'HAVE_METADATA';
41347        } else {
41348          this.state = 'HAVE_MASTER';
41349        }
41350      } else if (this.state === 'HAVE_CURRENT_METADATA') {
41351        this.state = 'HAVE_METADATA';
41352      }
41353    }
41354    /**
41355     * start loading of the playlist
41356     */
41357    ;
41358
41359    _proto.load = function load(shouldDelay) {
41360      var _this5 = this;
41361
41362      if (this.mediaUpdateTimeout) {
41363        window_1.clearTimeout(this.mediaUpdateTimeout);
41364        this.mediaUpdateTimeout = null;
41365      }
41366
41367      var media = this.media();
41368
41369      if (shouldDelay) {
41370        var delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
41371        this.mediaUpdateTimeout = window_1.setTimeout(function () {
41372          _this5.mediaUpdateTimeout = null;
41373
41374          _this5.load();
41375        }, delay);
41376        return;
41377      }
41378
41379      if (!this.started) {
41380        this.start();
41381        return;
41382      }
41383
41384      if (media && !media.endList) {
41385        this.trigger('mediaupdatetimeout');
41386      } else {
41387        this.trigger('loadedplaylist');
41388      }
41389    };
41390
41391    _proto.updateMediaUpdateTimeout_ = function updateMediaUpdateTimeout_(delay) {
41392      var _this6 = this;
41393
41394      if (this.mediaUpdateTimeout) {
41395        window_1.clearTimeout(this.mediaUpdateTimeout);
41396        this.mediaUpdateTimeout = null;
41397      } // we only have use mediaupdatetimeout for live playlists.
41398
41399
41400      if (!this.media() || this.media().endList) {
41401        return;
41402      }
41403
41404      this.mediaUpdateTimeout = window_1.setTimeout(function () {
41405        _this6.mediaUpdateTimeout = null;
41406
41407        _this6.trigger('mediaupdatetimeout');
41408
41409        _this6.updateMediaUpdateTimeout_(delay);
41410      }, delay);
41411    }
41412    /**
41413     * start loading of the playlist
41414     */
41415    ;
41416
41417    _proto.start = function start() {
41418      var _this7 = this;
41419
41420      this.started = true;
41421
41422      if (typeof this.src === 'object') {
41423        // in the case of an entirely constructed manifest object (meaning there's no actual
41424        // manifest on a server), default the uri to the page's href
41425        if (!this.src.uri) {
41426          this.src.uri = window_1.location.href;
41427        } // resolvedUri is added on internally after the initial request. Since there's no
41428        // request for pre-resolved manifests, add on resolvedUri here.
41429
41430
41431        this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
41432        // request can be skipped (since the top level of the manifest, at a minimum, is
41433        // already available as a parsed manifest object). However, if the manifest object
41434        // represents a master playlist, some media playlists may need to be resolved before
41435        // the starting segment list is available. Therefore, go directly to setup of the
41436        // initial playlist, and let the normal flow continue from there.
41437        //
41438        // Note that the call to setup is asynchronous, as other sections of VHS may assume
41439        // that the first request is asynchronous.
41440
41441        setTimeout(function () {
41442          _this7.setupInitialPlaylist(_this7.src);
41443        }, 0);
41444        return;
41445      } // request the specified URL
41446
41447
41448      this.request = this.vhs_.xhr({
41449        uri: this.src,
41450        withCredentials: this.withCredentials
41451      }, function (error, req) {
41452        // disposed
41453        if (!_this7.request) {
41454          return;
41455        } // clear the loader's request reference
41456
41457
41458        _this7.request = null;
41459
41460        if (error) {
41461          _this7.error = {
41462            status: req.status,
41463            message: "HLS playlist request error at URL: " + _this7.src + ".",
41464            responseText: req.responseText,
41465            // MEDIA_ERR_NETWORK
41466            code: 2
41467          };
41468
41469          if (_this7.state === 'HAVE_NOTHING') {
41470            _this7.started = false;
41471          }
41472
41473          return _this7.trigger('error');
41474        }
41475
41476        _this7.src = resolveManifestRedirect(_this7.handleManifestRedirects, _this7.src, req);
41477
41478        var manifest = _this7.parseManifest_({
41479          manifestString: req.responseText,
41480          url: _this7.src
41481        });
41482
41483        _this7.setupInitialPlaylist(manifest);
41484      });
41485    };
41486
41487    _proto.srcUri = function srcUri() {
41488      return typeof this.src === 'string' ? this.src : this.src.uri;
41489    }
41490    /**
41491     * Given a manifest object that's either a master or media playlist, trigger the proper
41492     * events and set the state of the playlist loader.
41493     *
41494     * If the manifest object represents a master playlist, `loadedplaylist` will be
41495     * triggered to allow listeners to select a playlist. If none is selected, the loader
41496     * will default to the first one in the playlists array.
41497     *
41498     * If the manifest object represents a media playlist, `loadedplaylist` will be
41499     * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
41500     *
41501     * In the case of a media playlist, a master playlist object wrapper with one playlist
41502     * will be created so that all logic can handle playlists in the same fashion (as an
41503     * assumed manifest object schema).
41504     *
41505     * @param {Object} manifest
41506     *        The parsed manifest object
41507     */
41508    ;
41509
41510    _proto.setupInitialPlaylist = function setupInitialPlaylist(manifest) {
41511      this.state = 'HAVE_MASTER';
41512
41513      if (manifest.playlists) {
41514        this.master = manifest;
41515        addPropertiesToMaster(this.master, this.srcUri()); // If the initial master playlist has playlists wtih segments already resolved,
41516        // then resolve URIs in advance, as they are usually done after a playlist request,
41517        // which may not happen if the playlist is resolved.
41518
41519        manifest.playlists.forEach(function (playlist) {
41520          playlist.segments = getAllSegments(playlist);
41521          playlist.segments.forEach(function (segment) {
41522            resolveSegmentUris(segment, playlist.resolvedUri);
41523          });
41524        });
41525        this.trigger('loadedplaylist');
41526
41527        if (!this.request) {
41528          // no media playlist was specifically selected so start
41529          // from the first listed one
41530          this.media(this.master.playlists[0]);
41531        }
41532
41533        return;
41534      } // In order to support media playlists passed in as vhs-json, the case where the uri
41535      // is not provided as part of the manifest should be considered, and an appropriate
41536      // default used.
41537
41538
41539      var uri = this.srcUri() || window_1.location.href;
41540      this.master = masterForMedia(manifest, uri);
41541      this.haveMetadata({
41542        playlistObject: manifest,
41543        url: uri,
41544        id: this.master.playlists[0].id
41545      });
41546      this.trigger('loadedmetadata');
41547    };
41548
41549    return PlaylistLoader;
41550  }(EventTarget$1);
41551  /**
41552   * @file xhr.js
41553   */
41554
41555
41556  var videojsXHR = videojs.xhr,
41557      mergeOptions$1 = videojs.mergeOptions;
41558
41559  var callbackWrapper = function callbackWrapper(request, error, response, callback) {
41560    var reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
41561
41562    if (!error && reqResponse) {
41563      request.responseTime = Date.now();
41564      request.roundTripTime = request.responseTime - request.requestTime;
41565      request.bytesReceived = reqResponse.byteLength || reqResponse.length;
41566
41567      if (!request.bandwidth) {
41568        request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
41569      }
41570    }
41571
41572    if (response.headers) {
41573      request.responseHeaders = response.headers;
41574    } // videojs.xhr now uses a specific code on the error
41575    // object to signal that a request has timed out instead
41576    // of setting a boolean on the request object
41577
41578
41579    if (error && error.code === 'ETIMEDOUT') {
41580      request.timedout = true;
41581    } // videojs.xhr no longer considers status codes outside of 200 and 0
41582    // (for file uris) to be errors, but the old XHR did, so emulate that
41583    // behavior. Status 206 may be used in response to byterange requests.
41584
41585
41586    if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
41587      error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
41588    }
41589
41590    callback(error, request);
41591  };
41592
41593  var xhrFactory = function xhrFactory() {
41594    var xhr = function XhrFunction(options, callback) {
41595      // Add a default timeout
41596      options = mergeOptions$1({
41597        timeout: 45e3
41598      }, options); // Allow an optional user-specified function to modify the option
41599      // object before we construct the xhr request
41600
41601      var beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest;
41602
41603      if (beforeRequest && typeof beforeRequest === 'function') {
41604        var newOptions = beforeRequest(options);
41605
41606        if (newOptions) {
41607          options = newOptions;
41608        }
41609      } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
41610      // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
41611
41612
41613      var xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr;
41614      var request = xhrMethod(options, function (error, response) {
41615        return callbackWrapper(request, error, response, callback);
41616      });
41617      var originalAbort = request.abort;
41618
41619      request.abort = function () {
41620        request.aborted = true;
41621        return originalAbort.apply(request, arguments);
41622      };
41623
41624      request.uri = options.uri;
41625      request.requestTime = Date.now();
41626      return request;
41627    };
41628
41629    xhr.original = true;
41630    return xhr;
41631  };
41632  /**
41633   * Turns segment byterange into a string suitable for use in
41634   * HTTP Range requests
41635   *
41636   * @param {Object} byterange - an object with two values defining the start and end
41637   *                             of a byte-range
41638   */
41639
41640
41641  var byterangeStr = function byterangeStr(byterange) {
41642    // `byterangeEnd` is one less than `offset + length` because the HTTP range
41643    // header uses inclusive ranges
41644    var byterangeEnd = byterange.offset + byterange.length - 1;
41645    var byterangeStart = byterange.offset;
41646    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
41647  };
41648  /**
41649   * Defines headers for use in the xhr request for a particular segment.
41650   *
41651   * @param {Object} segment - a simplified copy of the segmentInfo object
41652   *                           from SegmentLoader
41653   */
41654
41655
41656  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
41657    var headers = {};
41658
41659    if (segment.byterange) {
41660      headers.Range = byterangeStr(segment.byterange);
41661    }
41662
41663    return headers;
41664  };
41665  /**
41666   * @file bin-utils.js
41667   */
41668
41669  /**
41670   * convert a TimeRange to text
41671   *
41672   * @param {TimeRange} range the timerange to use for conversion
41673   * @param {number} i the iterator on the range to convert
41674   * @return {string} the range in string format
41675   */
41676
41677
41678  var textRange = function textRange(range, i) {
41679    return range.start(i) + '-' + range.end(i);
41680  };
41681  /**
41682   * format a number as hex string
41683   *
41684   * @param {number} e The number
41685   * @param {number} i the iterator
41686   * @return {string} the hex formatted number as a string
41687   */
41688
41689
41690  var formatHexString = function formatHexString(e, i) {
41691    var value = e.toString(16);
41692    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
41693  };
41694
41695  var formatAsciiString = function formatAsciiString(e) {
41696    if (e >= 0x20 && e < 0x7e) {
41697      return String.fromCharCode(e);
41698    }
41699
41700    return '.';
41701  };
41702  /**
41703   * Creates an object for sending to a web worker modifying properties that are TypedArrays
41704   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
41705   *
41706   * @param {Object} message
41707   *        Object of properties and values to send to the web worker
41708   * @return {Object}
41709   *         Modified message with TypedArray values expanded
41710   * @function createTransferableMessage
41711   */
41712
41713
41714  var createTransferableMessage = function createTransferableMessage(message) {
41715    var transferable = {};
41716    Object.keys(message).forEach(function (key) {
41717      var value = message[key];
41718
41719      if (ArrayBuffer.isView(value)) {
41720        transferable[key] = {
41721          bytes: value.buffer,
41722          byteOffset: value.byteOffset,
41723          byteLength: value.byteLength
41724        };
41725      } else {
41726        transferable[key] = value;
41727      }
41728    });
41729    return transferable;
41730  };
41731  /**
41732   * Returns a unique string identifier for a media initialization
41733   * segment.
41734   *
41735   * @param {Object} initSegment
41736   *        the init segment object.
41737   *
41738   * @return {string} the generated init segment id
41739   */
41740
41741
41742  var initSegmentId = function initSegmentId(initSegment) {
41743    var byterange = initSegment.byterange || {
41744      length: Infinity,
41745      offset: 0
41746    };
41747    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
41748  };
41749  /**
41750   * Returns a unique string identifier for a media segment key.
41751   *
41752   * @param {Object} key the encryption key
41753   * @return {string} the unique id for the media segment key.
41754   */
41755
41756
41757  var segmentKeyId = function segmentKeyId(key) {
41758    return key.resolvedUri;
41759  };
41760  /**
41761   * utils to help dump binary data to the console
41762   *
41763   * @param {Array|TypedArray} data
41764   *        data to dump to a string
41765   *
41766   * @return {string} the data as a hex string.
41767   */
41768
41769
41770  var hexDump = function hexDump(data) {
41771    var bytes = Array.prototype.slice.call(data);
41772    var step = 16;
41773    var result = '';
41774    var hex;
41775    var ascii;
41776
41777    for (var j = 0; j < bytes.length / step; j++) {
41778      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
41779      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
41780      result += hex + ' ' + ascii + '\n';
41781    }
41782
41783    return result;
41784  };
41785
41786  var tagDump = function tagDump(_ref) {
41787    var bytes = _ref.bytes;
41788    return hexDump(bytes);
41789  };
41790
41791  var textRanges = function textRanges(ranges) {
41792    var result = '';
41793    var i;
41794
41795    for (i = 0; i < ranges.length; i++) {
41796      result += textRange(ranges, i) + ' ';
41797    }
41798
41799    return result;
41800  };
41801
41802  var utils = /*#__PURE__*/Object.freeze({
41803    __proto__: null,
41804    createTransferableMessage: createTransferableMessage,
41805    initSegmentId: initSegmentId,
41806    segmentKeyId: segmentKeyId,
41807    hexDump: hexDump,
41808    tagDump: tagDump,
41809    textRanges: textRanges
41810  }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
41811  // 25% was arbitrarily chosen, and may need to be refined over time.
41812
41813  var SEGMENT_END_FUDGE_PERCENT = 0.25;
41814  /**
41815   * Converts a player time (any time that can be gotten/set from player.currentTime(),
41816   * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
41817   * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
41818   *
41819   * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
41820   * point" (a point where we have a mapping from program time to player time, with player
41821   * time being the post transmux start of the segment).
41822   *
41823   * For more details, see [this doc](../../docs/program-time-from-player-time.md).
41824   *
41825   * @param {number} playerTime the player time
41826   * @param {Object} segment the segment which contains the player time
41827   * @return {Date} program time
41828   */
41829
41830  var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
41831    if (!segment.dateTimeObject) {
41832      // Can't convert without an "anchor point" for the program time (i.e., a time that can
41833      // be used to map the start of a segment with a real world time).
41834      return null;
41835    }
41836
41837    var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
41838    var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
41839
41840    var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
41841    var offsetFromSegmentStart = playerTime - startOfSegment;
41842    return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
41843  };
41844
41845  var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
41846    return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
41847  };
41848  /**
41849   * Finds a segment that contains the time requested given as an ISO-8601 string. The
41850   * returned segment might be an estimate or an accurate match.
41851   *
41852   * @param {string} programTime The ISO-8601 programTime to find a match for
41853   * @param {Object} playlist A playlist object to search within
41854   */
41855
41856
41857  var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
41858    // Assumptions:
41859    //  - verifyProgramDateTimeTags has already been run
41860    //  - live streams have been started
41861    var dateTimeObject;
41862
41863    try {
41864      dateTimeObject = new Date(programTime);
41865    } catch (e) {
41866      return null;
41867    }
41868
41869    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41870      return null;
41871    }
41872
41873    var segment = playlist.segments[0];
41874
41875    if (dateTimeObject < segment.dateTimeObject) {
41876      // Requested time is before stream start.
41877      return null;
41878    }
41879
41880    for (var i = 0; i < playlist.segments.length - 1; i++) {
41881      segment = playlist.segments[i];
41882      var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
41883
41884      if (dateTimeObject < nextSegmentStart) {
41885        break;
41886      }
41887    }
41888
41889    var lastSegment = playlist.segments[playlist.segments.length - 1];
41890    var lastSegmentStart = lastSegment.dateTimeObject;
41891    var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
41892    var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
41893
41894    if (dateTimeObject > lastSegmentEnd) {
41895      // Beyond the end of the stream, or our best guess of the end of the stream.
41896      return null;
41897    }
41898
41899    if (dateTimeObject > lastSegmentStart) {
41900      segment = lastSegment;
41901    }
41902
41903    return {
41904      segment: segment,
41905      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
41906      // Although, given that all segments have accurate date time objects, the segment
41907      // selected should be accurate, unless the video has been transmuxed at some point
41908      // (determined by the presence of the videoTimingInfo object), the segment's "player
41909      // time" (the start time in the player) can't be considered accurate.
41910      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
41911    };
41912  };
41913  /**
41914   * Finds a segment that contains the given player time(in seconds).
41915   *
41916   * @param {number} time The player time to find a match for
41917   * @param {Object} playlist A playlist object to search within
41918   */
41919
41920
41921  var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
41922    // Assumptions:
41923    // - there will always be a segment.duration
41924    // - we can start from zero
41925    // - segments are in time order
41926    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41927      return null;
41928    }
41929
41930    var segmentEnd = 0;
41931    var segment;
41932
41933    for (var i = 0; i < playlist.segments.length; i++) {
41934      segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
41935      // should contain the most accurate values we have for the segment's player times.
41936      //
41937      // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
41938      // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
41939      // calculate an end value.
41940
41941      segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
41942
41943      if (time <= segmentEnd) {
41944        break;
41945      }
41946    }
41947
41948    var lastSegment = playlist.segments[playlist.segments.length - 1];
41949
41950    if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
41951      // The time requested is beyond the stream end.
41952      return null;
41953    }
41954
41955    if (time > segmentEnd) {
41956      // The time is within or beyond the last segment.
41957      //
41958      // Check to see if the time is beyond a reasonable guess of the end of the stream.
41959      if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
41960        // Technically, because the duration value is only an estimate, the time may still
41961        // exist in the last segment, however, there isn't enough information to make even
41962        // a reasonable estimate.
41963        return null;
41964      }
41965
41966      segment = lastSegment;
41967    }
41968
41969    return {
41970      segment: segment,
41971      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
41972      // Because videoTimingInfo is only set after transmux, it is the only way to get
41973      // accurate timing values.
41974      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
41975    };
41976  };
41977  /**
41978   * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
41979   * If the offset returned is positive, the programTime occurs after the
41980   * comparisonTimestamp.
41981   * If the offset is negative, the programTime occurs before the comparisonTimestamp.
41982   *
41983   * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
41984   * @param {string} programTime The programTime as an ISO-8601 string
41985   * @return {number} offset
41986   */
41987
41988
41989  var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
41990    var segmentDateTime;
41991    var programDateTime;
41992
41993    try {
41994      segmentDateTime = new Date(comparisonTimeStamp);
41995      programDateTime = new Date(programTime);
41996    } catch (e) {// TODO handle error
41997    }
41998
41999    var segmentTimeEpoch = segmentDateTime.getTime();
42000    var programTimeEpoch = programDateTime.getTime();
42001    return (programTimeEpoch - segmentTimeEpoch) / 1000;
42002  };
42003  /**
42004   * Checks that all segments in this playlist have programDateTime tags.
42005   *
42006   * @param {Object} playlist A playlist object
42007   */
42008
42009
42010  var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
42011    if (!playlist.segments || playlist.segments.length === 0) {
42012      return false;
42013    }
42014
42015    for (var i = 0; i < playlist.segments.length; i++) {
42016      var segment = playlist.segments[i];
42017
42018      if (!segment.dateTimeObject) {
42019        return false;
42020      }
42021    }
42022
42023    return true;
42024  };
42025  /**
42026   * Returns the programTime of the media given a playlist and a playerTime.
42027   * The playlist must have programDateTime tags for a programDateTime tag to be returned.
42028   * If the segments containing the time requested have not been buffered yet, an estimate
42029   * may be returned to the callback.
42030   *
42031   * @param {Object} args
42032   * @param {Object} args.playlist A playlist object to search within
42033   * @param {number} time A playerTime in seconds
42034   * @param {Function} callback(err, programTime)
42035   * @return {string} err.message A detailed error message
42036   * @return {Object} programTime
42037   * @return {number} programTime.mediaSeconds The streamTime in seconds
42038   * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
42039   */
42040
42041
42042  var getProgramTime = function getProgramTime(_ref) {
42043    var playlist = _ref.playlist,
42044        _ref$time = _ref.time,
42045        time = _ref$time === void 0 ? undefined : _ref$time,
42046        callback = _ref.callback;
42047
42048    if (!callback) {
42049      throw new Error('getProgramTime: callback must be provided');
42050    }
42051
42052    if (!playlist || time === undefined) {
42053      return callback({
42054        message: 'getProgramTime: playlist and time must be provided'
42055      });
42056    }
42057
42058    var matchedSegment = findSegmentForPlayerTime(time, playlist);
42059
42060    if (!matchedSegment) {
42061      return callback({
42062        message: 'valid programTime was not found'
42063      });
42064    }
42065
42066    if (matchedSegment.type === 'estimate') {
42067      return callback({
42068        message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
42069        seekTime: matchedSegment.estimatedStart
42070      });
42071    }
42072
42073    var programTimeObject = {
42074      mediaSeconds: time
42075    };
42076    var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
42077
42078    if (programTime) {
42079      programTimeObject.programDateTime = programTime.toISOString();
42080    }
42081
42082    return callback(null, programTimeObject);
42083  };
42084  /**
42085   * Seeks in the player to a time that matches the given programTime ISO-8601 string.
42086   *
42087   * @param {Object} args
42088   * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
42089   * @param {Object} args.playlist A playlist to look within
42090   * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
42091   * @param {Function} args.seekTo A method to perform a seek
42092   * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
42093   * @param {Object} args.tech The tech to seek on
42094   * @param {Function} args.callback(err, newTime) A callback to return the new time to
42095   * @return {string} err.message A detailed error message
42096   * @return {number} newTime The exact time that was seeked to in seconds
42097   */
42098
42099
42100  var seekToProgramTime = function seekToProgramTime(_ref2) {
42101    var programTime = _ref2.programTime,
42102        playlist = _ref2.playlist,
42103        _ref2$retryCount = _ref2.retryCount,
42104        retryCount = _ref2$retryCount === void 0 ? 2 : _ref2$retryCount,
42105        seekTo = _ref2.seekTo,
42106        _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
42107        pauseAfterSeek = _ref2$pauseAfterSeek === void 0 ? true : _ref2$pauseAfterSeek,
42108        tech = _ref2.tech,
42109        callback = _ref2.callback;
42110
42111    if (!callback) {
42112      throw new Error('seekToProgramTime: callback must be provided');
42113    }
42114
42115    if (typeof programTime === 'undefined' || !playlist || !seekTo) {
42116      return callback({
42117        message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
42118      });
42119    }
42120
42121    if (!playlist.endList && !tech.hasStarted_) {
42122      return callback({
42123        message: 'player must be playing a live stream to start buffering'
42124      });
42125    }
42126
42127    if (!verifyProgramDateTimeTags(playlist)) {
42128      return callback({
42129        message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
42130      });
42131    }
42132
42133    var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
42134
42135    if (!matchedSegment) {
42136      return callback({
42137        message: programTime + " was not found in the stream"
42138      });
42139    }
42140
42141    var segment = matchedSegment.segment;
42142    var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
42143
42144    if (matchedSegment.type === 'estimate') {
42145      // we've run out of retries
42146      if (retryCount === 0) {
42147        return callback({
42148          message: programTime + " is not buffered yet. Try again"
42149        });
42150      }
42151
42152      seekTo(matchedSegment.estimatedStart + mediaOffset);
42153      tech.one('seeked', function () {
42154        seekToProgramTime({
42155          programTime: programTime,
42156          playlist: playlist,
42157          retryCount: retryCount - 1,
42158          seekTo: seekTo,
42159          pauseAfterSeek: pauseAfterSeek,
42160          tech: tech,
42161          callback: callback
42162        });
42163      });
42164      return;
42165    } // Since the segment.start value is determined from the buffered end or ending time
42166    // of the prior segment, the seekToTime doesn't need to account for any transmuxer
42167    // modifications.
42168
42169
42170    var seekToTime = segment.start + mediaOffset;
42171
42172    var seekedCallback = function seekedCallback() {
42173      return callback(null, tech.currentTime());
42174    }; // listen for seeked event
42175
42176
42177    tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
42178
42179    if (pauseAfterSeek) {
42180      tech.pause();
42181    }
42182
42183    seekTo(seekToTime);
42184  }; // which will only happen if the request is complete.
42185
42186
42187  var callbackOnCompleted = function callbackOnCompleted(request, cb) {
42188    if (request.readyState === 4) {
42189      return cb();
42190    }
42191
42192    return;
42193  };
42194
42195  var containerRequest = function containerRequest(uri, xhr, cb) {
42196    var bytes = [];
42197    var id3Offset;
42198    var finished = false;
42199
42200    var endRequestAndCallback = function endRequestAndCallback(err, req, type, _bytes) {
42201      req.abort();
42202      finished = true;
42203      return cb(err, req, type, _bytes);
42204    };
42205
42206    var progressListener = function progressListener(error, request) {
42207      if (finished) {
42208        return;
42209      }
42210
42211      if (error) {
42212        return endRequestAndCallback(error, request, '', bytes);
42213      } // grap the new part of content that was just downloaded
42214
42215
42216      var newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
42217
42218      bytes = concatTypedArrays(bytes, stringToBytes(newPart, true));
42219      id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type
42220      // or we need at least two bytes after an id3Offset
42221
42222      if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
42223        return callbackOnCompleted(request, function () {
42224          return endRequestAndCallback(error, request, '', bytes);
42225        });
42226      }
42227
42228      var type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
42229      // to see the second sync byte, wait until we have enough data
42230      // before declaring it ts
42231
42232      if (type === 'ts' && bytes.length < 188) {
42233        return callbackOnCompleted(request, function () {
42234          return endRequestAndCallback(error, request, '', bytes);
42235        });
42236      } // this may be an unsynced ts segment
42237      // wait for 376 bytes before detecting no container
42238
42239
42240      if (!type && bytes.length < 376) {
42241        return callbackOnCompleted(request, function () {
42242          return endRequestAndCallback(error, request, '', bytes);
42243        });
42244      }
42245
42246      return endRequestAndCallback(null, request, type, bytes);
42247    };
42248
42249    var options = {
42250      uri: uri,
42251      beforeSend: function beforeSend(request) {
42252        // this forces the browser to pass the bytes to us unprocessed
42253        request.overrideMimeType('text/plain; charset=x-user-defined');
42254        request.addEventListener('progress', function (_ref) {
42255          _ref.total;
42256          _ref.loaded;
42257          return callbackWrapper(request, null, {
42258            statusCode: request.status
42259          }, progressListener);
42260        });
42261      }
42262    };
42263    var request = xhr(options, function (error, response) {
42264      return callbackWrapper(request, error, response, progressListener);
42265    });
42266    return request;
42267  };
42268
42269  var EventTarget = videojs.EventTarget,
42270      mergeOptions = videojs.mergeOptions;
42271
42272  var dashPlaylistUnchanged = function dashPlaylistUnchanged(a, b) {
42273    if (!isPlaylistUnchanged(a, b)) {
42274      return false;
42275    } // for dash the above check will often return true in scenarios where
42276    // the playlist actually has changed because mediaSequence isn't a
42277    // dash thing, and we often set it to 1. So if the playlists have the same amount
42278    // of segments we return true.
42279    // So for dash we need to make sure that the underlying segments are different.
42280    // if sidx changed then the playlists are different.
42281
42282
42283    if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
42284      return false;
42285    } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
42286      return false;
42287    } // one or the other does not have segments
42288    // there was a change.
42289
42290
42291    if (a.segments && !b.segments || !a.segments && b.segments) {
42292      return false;
42293    } // neither has segments nothing changed
42294
42295
42296    if (!a.segments && !b.segments) {
42297      return true;
42298    } // check segments themselves
42299
42300
42301    for (var i = 0; i < a.segments.length; i++) {
42302      var aSegment = a.segments[i];
42303      var bSegment = b.segments[i]; // if uris are different between segments there was a change
42304
42305      if (aSegment.uri !== bSegment.uri) {
42306        return false;
42307      } // neither segment has a byterange, there will be no byterange change.
42308
42309
42310      if (!aSegment.byterange && !bSegment.byterange) {
42311        continue;
42312      }
42313
42314      var aByterange = aSegment.byterange;
42315      var bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
42316
42317      if (aByterange && !bByterange || !aByterange && bByterange) {
42318        return false;
42319      } // if both segments have byterange with different offsets, there was a change.
42320
42321
42322      if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
42323        return false;
42324      }
42325    } // if everything was the same with segments, this is the same playlist.
42326
42327
42328    return true;
42329  };
42330  /**
42331   * Parses the master XML string and updates playlist URI references.
42332   *
42333   * @param {Object} config
42334   *        Object of arguments
42335   * @param {string} config.masterXml
42336   *        The mpd XML
42337   * @param {string} config.srcUrl
42338   *        The mpd URL
42339   * @param {Date} config.clientOffset
42340   *         A time difference between server and client
42341   * @param {Object} config.sidxMapping
42342   *        SIDX mappings for moof/mdat URIs and byte ranges
42343   * @return {Object}
42344   *         The parsed mpd manifest object
42345   */
42346
42347
42348  var parseMasterXml = function parseMasterXml(_ref) {
42349    var masterXml = _ref.masterXml,
42350        srcUrl = _ref.srcUrl,
42351        clientOffset = _ref.clientOffset,
42352        sidxMapping = _ref.sidxMapping;
42353    var master = parse(masterXml, {
42354      manifestUri: srcUrl,
42355      clientOffset: clientOffset,
42356      sidxMapping: sidxMapping
42357    });
42358    addPropertiesToMaster(master, srcUrl);
42359    return master;
42360  };
42361  /**
42362   * Returns a new master manifest that is the result of merging an updated master manifest
42363   * into the original version.
42364   *
42365   * @param {Object} oldMaster
42366   *        The old parsed mpd object
42367   * @param {Object} newMaster
42368   *        The updated parsed mpd object
42369   * @return {Object}
42370   *         A new object representing the original master manifest with the updated media
42371   *         playlists merged in
42372   */
42373
42374
42375  var updateMaster = function updateMaster(oldMaster, newMaster, sidxMapping) {
42376    var noChanges = true;
42377    var update = mergeOptions(oldMaster, {
42378      // These are top level properties that can be updated
42379      duration: newMaster.duration,
42380      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
42381    }); // First update the playlists in playlist list
42382
42383    for (var i = 0; i < newMaster.playlists.length; i++) {
42384      var playlist = newMaster.playlists[i];
42385
42386      if (playlist.sidx) {
42387        var sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
42388
42389        if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
42390          addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
42391        }
42392      }
42393
42394      var playlistUpdate = updateMaster$1(update, playlist, dashPlaylistUnchanged);
42395
42396      if (playlistUpdate) {
42397        update = playlistUpdate;
42398        noChanges = false;
42399      }
42400    } // Then update media group playlists
42401
42402
42403    forEachMediaGroup(newMaster, function (properties, type, group, label) {
42404      if (properties.playlists && properties.playlists.length) {
42405        var id = properties.playlists[0].id;
42406
42407        var _playlistUpdate = updateMaster$1(update, properties.playlists[0], dashPlaylistUnchanged);
42408
42409        if (_playlistUpdate) {
42410          update = _playlistUpdate; // update the playlist reference within media groups
42411
42412          update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
42413          noChanges = false;
42414        }
42415      }
42416    });
42417
42418    if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
42419      noChanges = false;
42420    }
42421
42422    if (noChanges) {
42423      return null;
42424    }
42425
42426    return update;
42427  }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
42428  // If the SIDXs have maps, the two maps should match,
42429  // both `a` and `b` missing SIDXs is considered matching.
42430  // If `a` or `b` but not both have a map, they aren't matching.
42431
42432
42433  var equivalentSidx = function equivalentSidx(a, b) {
42434    var neitherMap = Boolean(!a.map && !b.map);
42435    var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
42436    return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
42437  }; // exported for testing
42438
42439
42440  var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
42441    var newSidxMapping = {};
42442
42443    for (var id in playlists) {
42444      var playlist = playlists[id];
42445      var currentSidxInfo = playlist.sidx;
42446
42447      if (currentSidxInfo) {
42448        var key = generateSidxKey(currentSidxInfo);
42449
42450        if (!oldSidxMapping[key]) {
42451          break;
42452        }
42453
42454        var savedSidxInfo = oldSidxMapping[key].sidxInfo;
42455
42456        if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
42457          newSidxMapping[key] = oldSidxMapping[key];
42458        }
42459      }
42460    }
42461
42462    return newSidxMapping;
42463  };
42464  /**
42465   *  A function that filters out changed items as they need to be requested separately.
42466   *
42467   *  The method is exported for testing
42468   *
42469   *  @param {Object} master the parsed mpd XML returned via mpd-parser
42470   *  @param {Object} oldSidxMapping the SIDX to compare against
42471   */
42472
42473
42474  var filterChangedSidxMappings = function filterChangedSidxMappings(master, oldSidxMapping) {
42475    var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
42476    var mediaGroupSidx = videoSidx;
42477    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
42478      if (properties.playlists && properties.playlists.length) {
42479        var playlists = properties.playlists;
42480        mediaGroupSidx = mergeOptions(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
42481      }
42482    });
42483    return mediaGroupSidx;
42484  };
42485
42486  var DashPlaylistLoader = /*#__PURE__*/function (_EventTarget) {
42487    inheritsLoose(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
42488    // playlist loader setups from media groups will expect to be able to pass a playlist
42489    // (since there aren't external URLs to media playlists with DASH)
42490
42491
42492    function DashPlaylistLoader(srcUrlOrPlaylist, vhs, options, masterPlaylistLoader) {
42493      var _this;
42494
42495      if (options === void 0) {
42496        options = {};
42497      }
42498
42499      _this = _EventTarget.call(this) || this;
42500      _this.masterPlaylistLoader_ = masterPlaylistLoader || assertThisInitialized(_this);
42501
42502      if (!masterPlaylistLoader) {
42503        _this.isMaster_ = true;
42504      }
42505
42506      var _options = options,
42507          _options$withCredenti = _options.withCredentials,
42508          withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
42509          _options$handleManife = _options.handleManifestRedirects,
42510          handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
42511      _this.vhs_ = vhs;
42512      _this.withCredentials = withCredentials;
42513      _this.handleManifestRedirects = handleManifestRedirects;
42514
42515      if (!srcUrlOrPlaylist) {
42516        throw new Error('A non-empty playlist URL or object is required');
42517      } // event naming?
42518
42519
42520      _this.on('minimumUpdatePeriod', function () {
42521        _this.refreshXml_();
42522      }); // live playlist staleness timeout
42523
42524
42525      _this.on('mediaupdatetimeout', function () {
42526        _this.refreshMedia_(_this.media().id);
42527      });
42528
42529      _this.state = 'HAVE_NOTHING';
42530      _this.loadedPlaylists_ = {};
42531      _this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
42532      // The masterPlaylistLoader will be created with a string
42533
42534      if (_this.isMaster_) {
42535        _this.masterPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
42536        // once multi-period is refactored
42537
42538        _this.masterPlaylistLoader_.sidxMapping_ = {};
42539      } else {
42540        _this.childPlaylist_ = srcUrlOrPlaylist;
42541      }
42542
42543      return _this;
42544    }
42545
42546    var _proto = DashPlaylistLoader.prototype;
42547
42548    _proto.requestErrored_ = function requestErrored_(err, request, startingState) {
42549      // disposed
42550      if (!this.request) {
42551        return true;
42552      } // pending request is cleared
42553
42554
42555      this.request = null;
42556
42557      if (err) {
42558        // use the provided error object or create one
42559        // based on the request/response
42560        this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
42561          status: request.status,
42562          message: 'DASH request error at URL: ' + request.uri,
42563          response: request.response,
42564          // MEDIA_ERR_NETWORK
42565          code: 2
42566        };
42567
42568        if (startingState) {
42569          this.state = startingState;
42570        }
42571
42572        this.trigger('error');
42573        return true;
42574      }
42575    }
42576    /**
42577     * Verify that the container of the sidx segment can be parsed
42578     * and if it can, get and parse that segment.
42579     */
42580    ;
42581
42582    _proto.addSidxSegments_ = function addSidxSegments_(playlist, startingState, cb) {
42583      var _this2 = this;
42584
42585      var sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
42586
42587      if (!playlist.sidx || !sidxKey || this.masterPlaylistLoader_.sidxMapping_[sidxKey]) {
42588        // keep this function async
42589        this.mediaRequest_ = window_1.setTimeout(function () {
42590          return cb(false);
42591        }, 0);
42592        return;
42593      } // resolve the segment URL relative to the playlist
42594
42595
42596      var uri = resolveManifestRedirect(this.handleManifestRedirects, playlist.sidx.resolvedUri);
42597
42598      var fin = function fin(err, request) {
42599        if (_this2.requestErrored_(err, request, startingState)) {
42600          return;
42601        }
42602
42603        var sidxMapping = _this2.masterPlaylistLoader_.sidxMapping_;
42604        var sidx;
42605
42606        try {
42607          sidx = parseSidx_1(toUint8(request.response).subarray(8));
42608        } catch (e) {
42609          // sidx parsing failed.
42610          _this2.requestErrored_(e, request, startingState);
42611
42612          return;
42613        }
42614
42615        sidxMapping[sidxKey] = {
42616          sidxInfo: playlist.sidx,
42617          sidx: sidx
42618        };
42619        addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri);
42620        return cb(true);
42621      };
42622
42623      this.request = containerRequest(uri, this.vhs_.xhr, function (err, request, container, bytes) {
42624        if (err) {
42625          return fin(err, request);
42626        }
42627
42628        if (!container || container !== 'mp4') {
42629          return fin({
42630            status: request.status,
42631            message: "Unsupported " + (container || 'unknown') + " container type for sidx segment at URL: " + uri,
42632            // response is just bytes in this case
42633            // but we really don't want to return that.
42634            response: '',
42635            playlist: playlist,
42636            internal: true,
42637            blacklistDuration: Infinity,
42638            // MEDIA_ERR_NETWORK
42639            code: 2
42640          }, request);
42641        } // if we already downloaded the sidx bytes in the container request, use them
42642
42643
42644        var _playlist$sidx$bytera = playlist.sidx.byterange,
42645            offset = _playlist$sidx$bytera.offset,
42646            length = _playlist$sidx$bytera.length;
42647
42648        if (bytes.length >= length + offset) {
42649          return fin(err, {
42650            response: bytes.subarray(offset, offset + length),
42651            status: request.status,
42652            uri: request.uri
42653          });
42654        } // otherwise request sidx bytes
42655
42656
42657        _this2.request = _this2.vhs_.xhr({
42658          uri: uri,
42659          responseType: 'arraybuffer',
42660          headers: segmentXhrHeaders({
42661            byterange: playlist.sidx.byterange
42662          })
42663        }, fin);
42664      });
42665    };
42666
42667    _proto.dispose = function dispose() {
42668      this.trigger('dispose');
42669      this.stopRequest();
42670      this.loadedPlaylists_ = {};
42671      window_1.clearTimeout(this.minimumUpdatePeriodTimeout_);
42672      window_1.clearTimeout(this.mediaRequest_);
42673      window_1.clearTimeout(this.mediaUpdateTimeout);
42674      this.mediaUpdateTimeout = null;
42675      this.mediaRequest_ = null;
42676      this.minimumUpdatePeriodTimeout_ = null;
42677
42678      if (this.masterPlaylistLoader_.createMupOnMedia_) {
42679        this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
42680        this.masterPlaylistLoader_.createMupOnMedia_ = null;
42681      }
42682
42683      this.off();
42684    };
42685
42686    _proto.hasPendingRequest = function hasPendingRequest() {
42687      return this.request || this.mediaRequest_;
42688    };
42689
42690    _proto.stopRequest = function stopRequest() {
42691      if (this.request) {
42692        var oldRequest = this.request;
42693        this.request = null;
42694        oldRequest.onreadystatechange = null;
42695        oldRequest.abort();
42696      }
42697    };
42698
42699    _proto.media = function media(playlist) {
42700      var _this3 = this; // getter
42701
42702
42703      if (!playlist) {
42704        return this.media_;
42705      } // setter
42706
42707
42708      if (this.state === 'HAVE_NOTHING') {
42709        throw new Error('Cannot switch media playlist from ' + this.state);
42710      }
42711
42712      var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
42713
42714      if (typeof playlist === 'string') {
42715        if (!this.masterPlaylistLoader_.master.playlists[playlist]) {
42716          throw new Error('Unknown playlist URI: ' + playlist);
42717        }
42718
42719        playlist = this.masterPlaylistLoader_.master.playlists[playlist];
42720      }
42721
42722      var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
42723
42724      if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
42725        this.state = 'HAVE_METADATA';
42726        this.media_ = playlist; // trigger media change if the active media has been updated
42727
42728        if (mediaChange) {
42729          this.trigger('mediachanging');
42730          this.trigger('mediachange');
42731        }
42732
42733        return;
42734      } // switching to the active playlist is a no-op
42735
42736
42737      if (!mediaChange) {
42738        return;
42739      } // switching from an already loaded playlist
42740
42741
42742      if (this.media_) {
42743        this.trigger('mediachanging');
42744      }
42745
42746      this.addSidxSegments_(playlist, startingState, function (sidxChanged) {
42747        // everything is ready just continue to haveMetadata
42748        _this3.haveMetadata({
42749          startingState: startingState,
42750          playlist: playlist
42751        });
42752      });
42753    };
42754
42755    _proto.haveMetadata = function haveMetadata(_ref2) {
42756      var startingState = _ref2.startingState,
42757          playlist = _ref2.playlist;
42758      this.state = 'HAVE_METADATA';
42759      this.loadedPlaylists_[playlist.id] = playlist;
42760      this.mediaRequest_ = null; // This will trigger loadedplaylist
42761
42762      this.refreshMedia_(playlist.id);
42762 // fire loadedmetadata the first time a media playlist is loaded
42763      // to resolve setup of media groups
42764
42765      if (startingState === 'HAVE_MASTER') {
42766        this.trigger('loadedmetadata');
42767      } else {
42768        // trigger media change if the active media has been updated
42769        this.trigger('mediachange');
42770      }
42771    };
42772
42773    _proto.pause = function pause() {
42774      if (this.masterPlaylistLoader_.createMupOnMedia_) {
42775        this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
42776        this.masterPlaylistLoader_.createMupOnMedia_ = null;
42777      }
42778
42779      this.stopRequest();
42780      window_1.clearTimeout(this.mediaUpdateTimeout);
42781      this.mediaUpdateTimeout = null;
42782
42783      if (this.isMaster_) {
42784        window_1.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_);
42785        this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
42786      }
42787
42788      if (this.state === 'HAVE_NOTHING') {
42789        // If we pause the loader before any data has been retrieved, its as if we never
42790        // started, so reset to an unstarted state.
42791        this.started = false;
42792      }
42793    };
42794
42795    _proto.load = function load(isFinalRendition) {
42796      var _this4 = this;
42797
42798      window_1.clearTimeout(this.mediaUpdateTimeout);
42799      this.mediaUpdateTimeout = null;
42800      var media = this.media();
42801
42802      if (isFinalRendition) {
42803        var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
42804        this.mediaUpdateTimeout = window_1.setTimeout(function () {
42805          return _this4.load();
42806        }, delay);
42807        return;
42808      } // because the playlists are internal to the manifest, load should either load the
42809      // main manifest, or do nothing but trigger an event
42810
42811
42812      if (!this.started) {
42813        this.start();
42814        return;
42815      }
42816
42817      if (media && !media.endList) {
42818        // Check to see if this is the master loader and the MUP was cleared (this happens
42819        // when the loader was paused). `media` should be set at this point since one is always
42820        // set during `start()`.
42821        if (this.isMaster_ && !this.minimumUpdatePeriodTimeout_) {
42822          // Trigger minimumUpdatePeriod to refresh the master manifest
42823          this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
42824
42825          this.updateMinimumUpdatePeriodTimeout_();
42826        }
42827
42828        this.trigger('mediaupdatetimeout');
42829      } else {
42830        this.trigger('loadedplaylist');
42831      }
42832    };
42833
42834    _proto.start = function start() {
42835      var _this5 = this;
42836
42837      this.started = true; // We don't need to request the master manifest again
42838      // Call this asynchronously to match the xhr request behavior below
42839
42840      if (!this.isMaster_) {
42841        this.mediaRequest_ = window_1.setTimeout(function () {
42842          return _this5.haveMaster_();
42843        }, 0);
42844        return;
42845      }
42846
42847      this.requestMaster_(function (req, masterChanged) {
42848        _this5.haveMaster_();
42849
42850        if (!_this5.hasPendingRequest() && !_this5.media_) {
42851          _this5.media(_this5.masterPlaylistLoader_.master.playlists[0]);
42852        }
42853      });
42854    };
42855
42856    _proto.requestMaster_ = function requestMaster_(cb) {
42857      var _this6 = this;
42858
42859      this.request = this.vhs_.xhr({
42860        uri: this.masterPlaylistLoader_.srcUrl,
42861        withCredentials: this.withCredentials
42862      }, function (error, req) {
42863        if (_this6.requestErrored_(error, req)) {
42864          if (_this6.state === 'HAVE_NOTHING') {
42865            _this6.started = false;
42866          }
42867
42868          return;
42869        }
42870
42871        var masterChanged = req.responseText !== _this6.masterPlaylistLoader_.masterXml_;
42872        _this6.masterPlaylistLoader_.masterXml_ = req.responseText;
42873
42874        if (req.responseHeaders && req.responseHeaders.date) {
42875          _this6.masterLoaded_ = Date.parse(req.responseHeaders.date);
42876        } else {
42877          _this6.masterLoaded_ = Date.now();
42878        }
42879
42880        _this6.masterPlaylistLoader_.srcUrl = resolveManifestRedirect(_this6.handleManifestRedirects, _this6.masterPlaylistLoader_.srcUrl, req);
42881
42882        if (masterChanged) {
42883          _this6.handleMaster_();
42884
42885          _this6.syncClientServerClock_(function () {
42886            return cb(req, masterChanged);
42887          });
42888
42889          return;
42890        }
42891
42892        return cb(req, masterChanged);
42893      });
42894    }
42895    /**
42896     * Parses the master xml for UTCTiming node to sync the client clock to the server
42897     * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
42898     *
42899     * @param {Function} done
42900     *        Function to call when clock sync has completed
42901     */
42902    ;
42903
42904    _proto.syncClientServerClock_ = function syncClientServerClock_(done) {
42905      var _this7 = this;
42906
42907      var utcTiming = parseUTCTiming(this.masterPlaylistLoader_.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
42908      // server clock
42909
42910      if (utcTiming === null) {
42911        this.masterPlaylistLoader_.clientOffset_ = this.masterLoaded_ - Date.now();
42912        return done();
42913      }
42914
42915      if (utcTiming.method === 'DIRECT') {
42916        this.masterPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
42917        return done();
42918      }
42919
42920      this.request = this.vhs_.xhr({
42921        uri: resolveUrl(this.masterPlaylistLoader_.srcUrl, utcTiming.value),
42922        method: utcTiming.method,
42923        withCredentials: this.withCredentials
42924      }, function (error, req) {
42925        // disposed
42926        if (!_this7.request) {
42927          return;
42928        }
42929
42930        if (error) {
42931          // sync request failed, fall back to using date header from mpd
42932          // TODO: log warning
42933          _this7.masterPlaylistLoader_.clientOffset_ = _this7.masterLoaded_ - Date.now();
42934          return done();
42935        }
42936
42937        var serverTime;
42938
42939        if (utcTiming.method === 'HEAD') {
42940          if (!req.responseHeaders || !req.responseHeaders.date) {
42941            // expected date header not preset, fall back to using date header from mpd
42942            // TODO: log warning
42943            serverTime = _this7.masterLoaded_;
42944          } else {
42945            serverTime = Date.parse(req.responseHeaders.date);
42946          }
42947        } else {
42948          serverTime = Date.parse(req.responseText);
42949        }
42950
42951        _this7.masterPlaylistLoader_.clientOffset_ = serverTime - Date.now();
42952        done();
42953      });
42954    };
42955
42956    _proto.haveMaster_ = function haveMaster_() {
42957      this.state = 'HAVE_MASTER';
42958
42959      if (this.isMaster_) {
42960        // We have the master playlist at this point, so
42961        // trigger this to allow MasterPlaylistController
42962        // to make an initial playlist selection
42963        this.trigger('loadedplaylist');
42964      } else if (!this.media_) {
42965        // no media playlist was specifically selected so select
42966        // the one the child playlist loader was created with
42967        this.media(this.childPlaylist_);
42968      }
42969    };
42970
42971    _proto.handleMaster_ = function handleMaster_() {
42972      // clear media request
42973      this.mediaRequest_ = null;
42974      var newMaster = parseMasterXml({
42975        masterXml: this.masterPlaylistLoader_.masterXml_,
42976        srcUrl: this.masterPlaylistLoader_.srcUrl,
42977        clientOffset: this.masterPlaylistLoader_.clientOffset_,
42978        sidxMapping: this.masterPlaylistLoader_.sidxMapping_
42979      });
42980      var oldMaster = this.masterPlaylistLoader_.master; // if we have an old master to compare the new master against
42981
42982      if (oldMaster) {
42983        newMaster = updateMaster(oldMaster, newMaster, this.masterPlaylistLoader_.sidxMapping_);
42984      } // only update master if we have a new master
42985
42986
42987      this.masterPlaylistLoader_.master = newMaster ? newMaster : oldMaster;
42988      var location = this.masterPlaylistLoader_.master.locations && this.masterPlaylistLoader_.master.locations[0];
42989
42990      if (location && location !== this.masterPlaylistLoader_.srcUrl) {
42991        this.masterPlaylistLoader_.srcUrl = location;
42992      }
42993
42994      if (!oldMaster || newMaster && newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
42995        this.updateMinimumUpdatePeriodTimeout_();
42996      }
42997
42998      return Boolean(newMaster);
42999    };
43000
43001    _proto.updateMinimumUpdatePeriodTimeout_ = function updateMinimumUpdatePeriodTimeout_() {
43002      var mpl = this.masterPlaylistLoader_;
43002 // cancel any pending creation of mup on media
43003      // a new one will be added if needed.
43004
43005      if (mpl.createMupOnMedia_) {
43006        mpl.off('loadedmetadata', mpl.createMupOnMedia_);
43007        mpl.createMupOnMedia_ = null;
43008      } // clear any pending timeouts
43009
43010
43011      if (mpl.minimumUpdatePeriodTimeout_) {
43012        window_1.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
43013        mpl.minimumUpdatePeriodTimeout_ = null;
43014      }
43015
43016      var mup = mpl.master && mpl.master.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
43017      // MPD has no future validity, so a new one will need to be acquired when new
43018      // media segments are to be made available. Thus, we use the target duration
43019      // in this case
43020
43021      if (mup === 0) {
43022        if (mpl.media()) {
43023          mup = mpl.media().targetDuration * 1000;
43024        } else {
43025          mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
43026          mpl.one('loadedmetadata', mpl.createMupOnMedia_);
43027        }
43028      } // if minimumUpdatePeriod is invalid or <= zero, which
43029      // can happen when a live video becomes VOD. skip timeout
43030      // creation.
43031
43032
43033      if (typeof mup !== 'number' || mup <= 0) {
43034        if (mup < 0) {
43035          this.logger_("found invalid minimumUpdatePeriod of " + mup + ", not setting a timeout");
43036        }
43037
43038        return;
43039      }
43040
43041      this.createMUPTimeout_(mup);
43042    };
43043
43044    _proto.createMUPTimeout_ = function createMUPTimeout_(mup) {
43045      var mpl = this.masterPlaylistLoader_;
43046      mpl.minimumUpdatePeriodTimeout_ = window_1.setTimeout(function () {
43047        mpl.minimumUpdatePeriodTimeout_ = null;
43048        mpl.trigger('minimumUpdatePeriod');
43049        mpl.createMUPTimeout_(mup);
43050      }, mup);
43051    }
43052    /**
43053     * Sends request to refresh the master xml and updates the parsed master manifest
43054     */
43055    ;
43056
43057    _proto.refreshXml_ = function refreshXml_() {
43058      var _this8 = this;
43059
43060      this.requestMaster_(function (req, masterChanged) {
43061        if (!masterChanged) {
43062          return;
43063        }
43064
43065        if (_this8.media_) {
43066          _this8.media_ = _this8.masterPlaylistLoader_.master.playlists[_this8.media_.id];
43067        } // This will filter out updated sidx info from the mapping
43068
43069
43070        _this8.masterPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(_this8.masterPlaylistLoader_.master, _this8.masterPlaylistLoader_.sidxMapping_);
43071
43072        _this8.addSidxSegments_(_this8.media(), _this8.state, function (sidxChanged) {
43073          // TODO: do we need to reload the current playlist?
43074          _this8.refreshMedia_(_this8.media().id);
43075        });
43076      });
43077    }
43078    /**
43079     * Refreshes the media playlist by re-parsing the master xml and updating playlist
43080     * references. If this is an alternate loader, the updated parsed manifest is retrieved
43081     * from the master loader.
43082     */
43083    ;
43084
43085    _proto.refreshMedia_ = function refreshMedia_(mediaID) {
43086      var _this9 = this;
43087
43088      if (!mediaID) {
43089        throw new Error('refreshMedia_ must take a media id');
43090      } // for master we have to reparse the master xml
43091      // to re-create segments based on current timing values
43092      // which may change media. We only skip updating master
43093      // if this is the first time this.media_ is being set.
43094      // as master was just parsed in that case.
43095
43096
43097      if (this.media_ && this.isMaster_) {
43098        this.handleMaster_();
43099      }
43100
43101      var playlists = this.masterPlaylistLoader_.master.playlists;
43102      var mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
43103
43104      if (mediaChanged) {
43105        this.media_ = playlists[mediaID];
43106      } else {
43107        this.trigger('playlistunchanged');
43108      }
43109
43110      if (!this.mediaUpdateTimeout) {
43111        var createMediaUpdateTimeout = function createMediaUpdateTimeout() {
43112          if (_this9.media().endList) {
43113            return;
43114          }
43115
43116          _this9.mediaUpdateTimeout = window_1.setTimeout(function () {
43117            _this9.trigger('mediaupdatetimeout');
43118
43119            createMediaUpdateTimeout();
43120          }, refreshDelay(_this9.media(), Boolean(mediaChanged)));
43121        };
43122
43123        createMediaUpdateTimeout();
43124      }
43125
43126      this.trigger('loadedplaylist');
43127    };
43128
43129    return DashPlaylistLoader;
43130  }(EventTarget);
43131
43132  var Config = {
43133    GOAL_BUFFER_LENGTH: 30,
43134    MAX_GOAL_BUFFER_LENGTH: 60,
43135    BACK_BUFFER_LENGTH: 30,
43136    GOAL_BUFFER_LENGTH_RATE: 1,
43137    // 0.5 MB/s
43138    INITIAL_BANDWIDTH: 4194304,
43139    // A fudge factor to apply to advertised playlist bitrates to account for
43140    // temporary flucations in client bandwidth
43141    BANDWIDTH_VARIANCE: 1.2,
43142    // How much of the buffer must be filled before we consider upswitching
43143    BUFFER_LOW_WATER_LINE: 0,
43144    MAX_BUFFER_LOW_WATER_LINE: 30,
43145    // TODO: Remove this when experimentalBufferBasedABR is removed
43146    EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
43147    BUFFER_LOW_WATER_LINE_RATE: 1,
43148    // If the buffer is greater than the high water line, we won't switch down
43149    BUFFER_HIGH_WATER_LINE: 30
43150  };
43151
43152  var stringToArrayBuffer = function stringToArrayBuffer(string) {
43153    var view = new Uint8Array(new ArrayBuffer(string.length));
43154
43155    for (var i = 0; i < string.length; i++) {
43156      view[i] = string.charCodeAt(i);
43157    }
43158
43159    return view.buffer;
43160  };
43161  /* global Blob, BlobBuilder, Worker */
43162  // unify worker interface
43163
43164
43165  var browserWorkerPolyFill = function browserWorkerPolyFill(workerObj) {
43166    // node only supports on/off
43167    workerObj.on = workerObj.addEventListener;
43168    workerObj.off = workerObj.removeEventListener;
43169    return workerObj;
43170  };
43171
43172  var createObjectURL = function createObjectURL(str) {
43173    try {
43174      return URL.createObjectURL(new Blob([str], {
43175        type: 'application/javascript'
43176      }));
43177    } catch (e) {
43178      var blob = new BlobBuilder();
43179      blob.append(str);
43180      return URL.createObjectURL(blob.getBlob());
43181    }
43182  };
43183
43184  var factory = function factory(code) {
43185    return function () {
43186      var objectUrl = createObjectURL(code);
43187      var worker = browserWorkerPolyFill(new Worker(objectUrl));
43188      worker.objURL = objectUrl;
43189      var terminate = worker.terminate;
43190      worker.on = worker.addEventListener;
43191      worker.off = worker.removeEventListener;
43192
43193      worker.terminate = function () {
43194        URL.revokeObjectURL(objectUrl);
43195        return terminate.call(this);
43196      };
43197
43198      return worker;
43199    };
43200  };
43201
43202  var transform = function transform(code) {
43203    return "var browserWorkerPolyFill = " + browserWorkerPolyFill.toString() + ";\n" + 'browserWorkerPolyFill(self);\n' + code;
43204  };
43205
43206  var getWorkerString = function getWorkerString(fn) {
43207    return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
43208  };
43209  /* rollup-plugin-worker-factory start for worker!/Users/bcasey/Projects/videojs-http-streaming/src/transmuxer-worker.js */
43210
43211
43212  var workerCode$1 = transform(getWorkerString(function () {
43213    /**
43214     * mux.js
43215     *
43216     * Copyright (c) Brightcove
43217     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43218     *
43219     * A lightweight readable stream implemention that handles event dispatching.
43220     * Objects that inherit from streams should call init in their constructors.
43221     */
43222    var Stream = function Stream() {
43223      this.init = function () {
43224        var listeners = {};
43225        /**
43226         * Add a listener for a specified event type.
43227         * @param type {string} the event name
43228         * @param listener {function} the callback to be invoked when an event of
43229         * the specified type occurs
43230         */
43231
43232        this.on = function (type, listener) {
43233          if (!listeners[type]) {
43234            listeners[type] = [];
43235          }
43236
43237          listeners[type] = listeners[type].concat(listener);
43238        };
43239        /**
43240         * Remove a listener for a specified event type.
43241         * @param type {string} the event name
43242         * @param listener {function} a function previously registered for this
43243         * type of event through `on`
43244         */
43245
43246
43247        this.off = function (type, listener) {
43248          var index;
43249
43250          if (!listeners[type]) {
43251            return false;
43252          }
43253
43254          index = listeners[type].indexOf(listener);
43255          listeners[type] = listeners[type].slice();
43256          listeners[type].splice(index, 1);
43257          return index > -1;
43258        };
43259        /**
43260         * Trigger an event of the specified type on this stream. Any additional
43261         * arguments to this function are passed as parameters to event listeners.
43262         * @param type {string} the event name
43263         */
43264
43265
43266        this.trigger = function (type) {
43267          var callbacks, i, length, args;
43268          callbacks = listeners[type];
43269
43270          if (!callbacks) {
43271            return;
43272          } // Slicing the arguments on every invocation of this method
43273          // can add a significant amount of overhead. Avoid the
43274          // intermediate object creation for the common case of a
43275          // single callback argument
43276
43277
43278          if (arguments.length === 2) {
43279            length = callbacks.length;
43280
43281            for (i = 0; i < length; ++i) {
43282              callbacks[i].call(this, arguments[1]);
43283            }
43284          } else {
43285            args = [];
43286            i = arguments.length;
43287
43288            for (i = 1; i < arguments.length; ++i) {
43289              args.push(arguments[i]);
43290            }
43291
43292            length = callbacks.length;
43293
43294            for (i = 0; i < length; ++i) {
43295              callbacks[i].apply(this, args);
43296            }
43297          }
43298        };
43299        /**
43300         * Destroys the stream and cleans up.
43301         */
43302
43303
43304        this.dispose = function () {
43305          listeners = {};
43306        };
43307      };
43308    };
43309    /**
43310     * Forwards all `data` events on this stream to the destination stream. The
43311     * destination stream should provide a method `push` to receive the data
43312     * events as they arrive.
43313     * @param destination {stream} the stream that will receive all `data` events
43314     * @param autoFlush {boolean} if false, we will not call `flush` on the destination
43315     *                            when the current stream emits a 'done' event
43316     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
43317     */
43318
43319
43320    Stream.prototype.pipe = function (destination) {
43321      this.on('data', function (data) {
43322        destination.push(data);
43323      });
43324      this.on('done', function (flushSource) {
43325        destination.flush(flushSource);
43326      });
43327      this.on('partialdone', function (flushSource) {
43328        destination.partialFlush(flushSource);
43329      });
43330      this.on('endedtimeline', function (flushSource) {
43331        destination.endTimeline(flushSource);
43332      });
43333      this.on('reset', function (flushSource) {
43334        destination.reset(flushSource);
43335      });
43336      return destination;
43337    }; // Default stream functions that are expected to be overridden to perform
43338    // actual work. These are provided by the prototype as a sort of no-op
43339    // implementation so that we don't have to check for their existence in the
43340    // `pipe` function above.
43341
43342
43343    Stream.prototype.push = function (data) {
43344      this.trigger('data', data);
43345    };
43346
43347    Stream.prototype.flush = function (flushSource) {
43348      this.trigger('done', flushSource);
43349    };
43350
43351    Stream.prototype.partialFlush = function (flushSource) {
43352      this.trigger('partialdone', flushSource);
43353    };
43354
43355    Stream.prototype.endTimeline = function (flushSource) {
43356      this.trigger('endedtimeline', flushSource);
43357    };
43358
43359    Stream.prototype.reset = function (flushSource) {
43360      this.trigger('reset', flushSource);
43361    };
43362
43363    var stream = Stream;
43364    /**
43365     * mux.js
43366     *
43367     * Copyright (c) Brightcove
43368     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43369     *
43370     * Functions that generate fragmented MP4s suitable for use with Media
43371     * Source Extensions.
43372     */
43373
43374    var UINT32_MAX = Math.pow(2, 32) - 1;
43375    var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun$1, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
43376
43377    (function () {
43378      var i;
43379      types = {
43380        avc1: [],
43381        // codingname
43382        avcC: [],
43383        btrt: [],
43384        dinf: [],
43385        dref: [],
43386        esds: [],
43387        ftyp: [],
43388        hdlr: [],
43389        mdat: [],
43390        mdhd: [],
43391        mdia: [],
43392        mfhd: [],
43393        minf: [],
43394        moof: [],
43395        moov: [],
43396        mp4a: [],
43397        // codingname
43398        mvex: [],
43399        mvhd: [],
43400        pasp: [],
43401        sdtp: [],
43402        smhd: [],
43403        stbl: [],
43404        stco: [],
43405        stsc: [],
43406        stsd: [],
43407        stsz: [],
43408        stts: [],
43409        styp: [],
43410        tfdt: [],
43411        tfhd: [],
43412        traf: [],
43413        trak: [],
43414        trun: [],
43415        trex: [],
43416        tkhd: [],
43417        vmhd: []
43418      }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
43419      // don't throw an error
43420
43421      if (typeof Uint8Array === 'undefined') {
43422        return;
43423      }
43424
43425      for (i in types) {
43426        if (types.hasOwnProperty(i)) {
vendor: 56,075 bytes, lines 43427-44921
43427          types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
43428        }
43429      }
43430
43431      MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
43432      AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
43433      MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
43434      VIDEO_HDLR = new Uint8Array([0x00, // version 0
43435      0x00, 0x00, 0x00, // flags
43436      0x00, 0x00, 0x00, 0x00, // pre_defined
43437      0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
43438      0x00, 0x00, 0x00, 0x00, // reserved
43439      0x00, 0x00, 0x00, 0x00, // reserved
43440      0x00, 0x00, 0x00, 0x00, // reserved
43441      0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
43442      ]);
43443      AUDIO_HDLR = new Uint8Array([0x00, // version 0
43444      0x00, 0x00, 0x00, // flags
43445      0x00, 0x00, 0x00, 0x00, // pre_defined
43446      0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
43447      0x00, 0x00, 0x00, 0x00, // reserved
43448      0x00, 0x00, 0x00, 0x00, // reserved
43449      0x00, 0x00, 0x00, 0x00, // reserved
43450      0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
43451      ]);
43452      HDLR_TYPES = {
43453        video: VIDEO_HDLR,
43454        audio: AUDIO_HDLR
43455      };
43456      DREF = new Uint8Array([0x00, // version 0
43457      0x00, 0x00, 0x00, // flags
43458      0x00, 0x00, 0x00, 0x01, // entry_count
43459      0x00, 0x00, 0x00, 0x0c, // entry_size
43460      0x75, 0x72, 0x6c, 0x20, // 'url' type
43461      0x00, // version 0
43462      0x00, 0x00, 0x01 // entry_flags
43463      ]);
43464      SMHD = new Uint8Array([0x00, // version
43465      0x00, 0x00, 0x00, // flags
43466      0x00, 0x00, // balance, 0 means centered
43467      0x00, 0x00 // reserved
43468      ]);
43469      STCO = new Uint8Array([0x00, // version
43470      0x00, 0x00, 0x00, // flags
43471      0x00, 0x00, 0x00, 0x00 // entry_count
43472      ]);
43473      STSC = STCO;
43474      STSZ = new Uint8Array([0x00, // version
43475      0x00, 0x00, 0x00, // flags
43476      0x00, 0x00, 0x00, 0x00, // sample_size
43477      0x00, 0x00, 0x00, 0x00 // sample_count
43478      ]);
43479      STTS = STCO;
43480      VMHD = new Uint8Array([0x00, // version
43481      0x00, 0x00, 0x01, // flags
43482      0x00, 0x00, // graphicsmode
43483      0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
43484      ]);
43485    })();
43486
43487    box = function box(type) {
43488      var payload = [],
43489          size = 0,
43490          i,
43491          result,
43492          view;
43493
43494      for (i = 1; i < arguments.length; i++) {
43495        payload.push(arguments[i]);
43496      }
43497
43498      i = payload.length; // calculate the total size we need to allocate
43499
43500      while (i--) {
43501        size += payload[i].byteLength;
43502      }
43503
43504      result = new Uint8Array(size + 8);
43505      view = new DataView(result.buffer, result.byteOffset, result.byteLength);
43506      view.setUint32(0, result.byteLength);
43507      result.set(type, 4); // copy the payload into the result
43508
43509      for (i = 0, size = 8; i < payload.length; i++) {
43510        result.set(payload[i], size);
43511        size += payload[i].byteLength;
43512      }
43513
43514      return result;
43515    };
43516
43517    dinf = function dinf() {
43518      return box(types.dinf, box(types.dref, DREF));
43519    };
43520
43521    esds = function esds(track) {
43522      return box(types.esds, new Uint8Array([0x00, // version
43523      0x00, 0x00, 0x00, // flags
43524      // ES_Descriptor
43525      0x03, // tag, ES_DescrTag
43526      0x19, // length
43527      0x00, 0x00, // ES_ID
43528      0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
43529      // DecoderConfigDescriptor
43530      0x04, // tag, DecoderConfigDescrTag
43531      0x11, // length
43532      0x40, // object type
43533      0x15, // streamType
43534      0x00, 0x06, 0x00, // bufferSizeDB
43535      0x00, 0x00, 0xda, 0xc0, // maxBitrate
43536      0x00, 0x00, 0xda, 0xc0, // avgBitrate
43537      // DecoderSpecificInfo
43538      0x05, // tag, DecoderSpecificInfoTag
43539      0x02, // length
43540      // ISO/IEC 14496-3, AudioSpecificConfig
43541      // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
43542      track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
43543      ]));
43544    };
43545
43546    ftyp = function ftyp() {
43547      return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
43548    };
43549
43550    hdlr = function hdlr(type) {
43551      return box(types.hdlr, HDLR_TYPES[type]);
43552    };
43553
43554    mdat = function mdat(data) {
43555      return box(types.mdat, data);
43556    };
43557
43558    mdhd = function mdhd(track) {
43559      var result = new Uint8Array([0x00, // version 0
43560      0x00, 0x00, 0x00, // flags
43561      0x00, 0x00, 0x00, 0x02, // creation_time
43562      0x00, 0x00, 0x00, 0x03, // modification_time
43563      0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
43564      track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
43565      0x55, 0xc4, // 'und' language (undetermined)
43566      0x00, 0x00]); // Use the sample rate from the track metadata, when it is
43567      // defined. The sample rate can be parsed out of an ADTS header, for
43568      // instance.
43569
43570      if (track.samplerate) {
43571        result[12] = track.samplerate >>> 24 & 0xFF;
43572        result[13] = track.samplerate >>> 16 & 0xFF;
43573        result[14] = track.samplerate >>> 8 & 0xFF;
43574        result[15] = track.samplerate & 0xFF;
43575      }
43576
43577      return box(types.mdhd, result);
43578    };
43579
43580    mdia = function mdia(track) {
43581      return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
43582    };
43583
43584    mfhd = function mfhd(sequenceNumber) {
43585      return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
43586      (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
43587      ]));
43588    };
43589
43590    minf = function minf(track) {
43591      return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
43592    };
43593
43594    moof = function moof(sequenceNumber, tracks) {
43595      var trackFragments = [],
43596          i = tracks.length; // build traf boxes for each track fragment
43597
43598      while (i--) {
43599        trackFragments[i] = traf(tracks[i]);
43600      }
43601
43602      return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
43603    };
43604    /**
43605     * Returns a movie box.
43606     * @param tracks {array} the tracks associated with this movie
43607     * @see ISO/IEC 14496-12:2012(E), section 8.2.1
43608     */
43609
43610
43611    moov = function moov(tracks) {
43612      var i = tracks.length,
43613          boxes = [];
43614
43615      while (i--) {
43616        boxes[i] = trak(tracks[i]);
43617      }
43618
43619      return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
43620    };
43621
43622    mvex = function mvex(tracks) {
43623      var i = tracks.length,
43624          boxes = [];
43625
43626      while (i--) {
43627        boxes[i] = trex(tracks[i]);
43628      }
43629
43630      return box.apply(null, [types.mvex].concat(boxes));
43631    };
43632
43633    mvhd = function mvhd(duration) {
43634      var bytes = new Uint8Array([0x00, // version 0
43635      0x00, 0x00, 0x00, // flags
43636      0x00, 0x00, 0x00, 0x01, // creation_time
43637      0x00, 0x00, 0x00, 0x02, // modification_time
43638      0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
43639      (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
43640      0x00, 0x01, 0x00, 0x00, // 1.0 rate
43641      0x01, 0x00, // 1.0 volume
43642      0x00, 0x00, // reserved
43643      0x00, 0x00, 0x00, 0x00, // reserved
43644      0x00, 0x00, 0x00, 0x00, // reserved
43645      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
43646      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
43647      0xff, 0xff, 0xff, 0xff // next_track_ID
43648      ]);
43649      return box(types.mvhd, bytes);
43650    };
43651
43652    sdtp = function sdtp(track) {
43653      var samples = track.samples || [],
43654          bytes = new Uint8Array(4 + samples.length),
43655          flags,
43656          i; // leave the full box header (4 bytes) all zero
43657      // write the sample table
43658
43659      for (i = 0; i < samples.length; i++) {
43660        flags = samples[i].flags;
43661        bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
43662      }
43663
43664      return box(types.sdtp, bytes);
43665    };
43666
43667    stbl = function stbl(track) {
43668      return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
43669    };
43670
43671    (function () {
43672      var videoSample, audioSample;
43673
43674      stsd = function stsd(track) {
43675        return box(types.stsd, new Uint8Array([0x00, // version 0
43676        0x00, 0x00, 0x00, // flags
43677        0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
43678      };
43679
43680      videoSample = function videoSample(track) {
43681        var sps = track.sps || [],
43682            pps = track.pps || [],
43683            sequenceParameterSets = [],
43684            pictureParameterSets = [],
43685            i,
43686            avc1Box; // assemble the SPSs
43687
43688        for (i = 0; i < sps.length; i++) {
43689          sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
43690          sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
43691
43692          sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
43693        } // assemble the PPSs
43694
43695
43696        for (i = 0; i < pps.length; i++) {
43697          pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
43698          pictureParameterSets.push(pps[i].byteLength & 0xFF);
43699          pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
43700        }
43701
43702        avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43703        0x00, 0x01, // data_reference_index
43704        0x00, 0x00, // pre_defined
43705        0x00, 0x00, // reserved
43706        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
43707        (track.width & 0xff00) >> 8, track.width & 0xff, // width
43708        (track.height & 0xff00) >> 8, track.height & 0xff, // height
43709        0x00, 0x48, 0x00, 0x00, // horizresolution
43710        0x00, 0x48, 0x00, 0x00, // vertresolution
43711        0x00, 0x00, 0x00, 0x00, // reserved
43712        0x00, 0x01, // frame_count
43713        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
43714        0x00, 0x18, // depth = 24
43715        0x11, 0x11 // pre_defined = -1
43716        ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
43717        track.profileIdc, // AVCProfileIndication
43718        track.profileCompatibility, // profile_compatibility
43719        track.levelIdc, // AVCLevelIndication
43720        0xff // lengthSizeMinusOne, hard-coded to 4 bytes
43721        ].concat([sps.length], // numOfSequenceParameterSets
43722        sequenceParameterSets, // "SPS"
43723        [pps.length], // numOfPictureParameterSets
43724        pictureParameterSets // "PPS"
43725        ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
43726        0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
43727        0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
43728        ]))];
43729
43730        if (track.sarRatio) {
43731          var hSpacing = track.sarRatio[0],
43732              vSpacing = track.sarRatio[1];
43733          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])));
43734        }
43735
43736        return box.apply(null, avc1Box);
43737      };
43738
43739      audioSample = function audioSample(track) {
43740        return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
43741        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43742        0x00, 0x01, // data_reference_index
43743        // AudioSampleEntry, ISO/IEC 14496-12
43744        0x00, 0x00, 0x00, 0x00, // reserved
43745        0x00, 0x00, 0x00, 0x00, // reserved
43746        (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
43747        (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
43748        0x00, 0x00, // pre_defined
43749        0x00, 0x00, // reserved
43750        (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
43751        // MP4AudioSampleEntry, ISO/IEC 14496-14
43752        ]), esds(track));
43753      };
43754    })();
43755
43756    tkhd = function tkhd(track) {
43757      var result = new Uint8Array([0x00, // version 0
43758      0x00, 0x00, 0x07, // flags
43759      0x00, 0x00, 0x00, 0x00, // creation_time
43760      0x00, 0x00, 0x00, 0x00, // modification_time
43761      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43762      0x00, 0x00, 0x00, 0x00, // reserved
43763      (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
43764      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
43765      0x00, 0x00, // layer
43766      0x00, 0x00, // alternate_group
43767      0x01, 0x00, // non-audio track volume
43768      0x00, 0x00, // reserved
43769      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
43770      (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
43771      (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
43772      ]);
43773      return box(types.tkhd, result);
43774    };
43775    /**
43776     * Generate a track fragment (traf) box. A traf box collects metadata
43777     * about tracks in a movie fragment (moof) box.
43778     */
43779
43780
43781    traf = function traf(track) {
43782      var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
43783      trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
43784      0x00, 0x00, 0x3a, // flags
43785      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43786      0x00, 0x00, 0x00, 0x01, // sample_description_index
43787      0x00, 0x00, 0x00, 0x00, // default_sample_duration
43788      0x00, 0x00, 0x00, 0x00, // default_sample_size
43789      0x00, 0x00, 0x00, 0x00 // default_sample_flags
43790      ]));
43791      upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
43792      lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
43793      trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
43794      0x00, 0x00, 0x00, // flags
43795      // baseMediaDecodeTime
43796      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
43797      // the containing moof to the first payload byte of the associated
43798      // mdat
43799
43800      dataOffset = 32 + // tfhd
43801      20 + // tfdt
43802      8 + // traf header
43803      16 + // mfhd
43804      8 + // moof header
43805      8; // mdat header
43806      // audio tracks require less metadata
43807
43808      if (track.type === 'audio') {
43809        trackFragmentRun = trun$1(track, dataOffset);
43810        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
43811      } // video tracks should contain an independent and disposable samples
43812      // box (sdtp)
43813      // generate one and adjust offsets to match
43814
43815
43816      sampleDependencyTable = sdtp(track);
43817      trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
43818      return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
43819    };
43820    /**
43821     * Generate a track box.
43822     * @param track {object} a track definition
43823     * @return {Uint8Array} the track box
43824     */
43825
43826
43827    trak = function trak(track) {
43828      track.duration = track.duration || 0xffffffff;
43829      return box(types.trak, tkhd(track), mdia(track));
43830    };
43831
43832    trex = function trex(track) {
43833      var result = new Uint8Array([0x00, // version 0
43834      0x00, 0x00, 0x00, // flags
43835      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
43836      0x00, 0x00, 0x00, 0x01, // default_sample_description_index
43837      0x00, 0x00, 0x00, 0x00, // default_sample_duration
43838      0x00, 0x00, 0x00, 0x00, // default_sample_size
43839      0x00, 0x01, 0x00, 0x01 // default_sample_flags
43840      ]); // the last two bytes of default_sample_flags is the sample
43841      // degradation priority, a hint about the importance of this sample
43842      // relative to others. Lower the degradation priority for all sample
43843      // types other than video.
43844
43845      if (track.type !== 'video') {
43846        result[result.length - 1] = 0x00;
43847      }
43848
43849      return box(types.trex, result);
43850    };
43851
43852    (function () {
43853      var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
43854      // duration is present for the first sample, it will be present for
43855      // all subsequent samples.
43856      // see ISO/IEC 14496-12:2012, Section 8.8.8.1
43857
43858      trunHeader = function trunHeader(samples, offset) {
43859        var durationPresent = 0,
43860            sizePresent = 0,
43861            flagsPresent = 0,
43862            compositionTimeOffset = 0; // trun flag constants
43863
43864        if (samples.length) {
43865          if (samples[0].duration !== undefined) {
43866            durationPresent = 0x1;
43867          }
43868
43869          if (samples[0].size !== undefined) {
43870            sizePresent = 0x2;
43871          }
43872
43873          if (samples[0].flags !== undefined) {
43874            flagsPresent = 0x4;
43875          }
43876
43877          if (samples[0].compositionTimeOffset !== undefined) {
43878            compositionTimeOffset = 0x8;
43879          }
43880        }
43881
43882        return [0x00, // version 0
43883        0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
43884        (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
43885        (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
43886        ];
43887      };
43888
43889      videoTrun = function videoTrun(track, offset) {
43890        var bytesOffest, bytes, header, samples, sample, i;
43891        samples = track.samples || [];
43892        offset += 8 + 12 + 16 * samples.length;
43893        header = trunHeader(samples, offset);
43894        bytes = new Uint8Array(header.length + samples.length * 16);
43895        bytes.set(header);
43896        bytesOffest = header.length;
43897
43898        for (i = 0; i < samples.length; i++) {
43899          sample = samples[i];
43900          bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43901          bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43902          bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43903          bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43904
43905          bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43906          bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43907          bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43908          bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43909
43910          bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
43911          bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
43912          bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
43913          bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
43914
43915          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
43916          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
43917          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
43918          bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
43919        }
43920
43921        return box(types.trun, bytes);
43922      };
43923
43924      audioTrun = function audioTrun(track, offset) {
43925        var bytes, bytesOffest, header, samples, sample, i;
43926        samples = track.samples || [];
43927        offset += 8 + 12 + 8 * samples.length;
43928        header = trunHeader(samples, offset);
43929        bytes = new Uint8Array(header.length + samples.length * 8);
43930        bytes.set(header);
43931        bytesOffest = header.length;
43932
43933        for (i = 0; i < samples.length; i++) {
43934          sample = samples[i];
43935          bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43936          bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43937          bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43938          bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43939
43940          bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43941          bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43942          bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43943          bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43944        }
43945
43946        return box(types.trun, bytes);
43947      };
43948
43949      trun$1 = function trun(track, offset) {
43950        if (track.type === 'audio') {
43951          return audioTrun(track, offset);
43952        }
43953
43954        return videoTrun(track, offset);
43955      };
43956    })();
43957
43958    var mp4Generator = {
43959      ftyp: ftyp,
43960      mdat: mdat,
43961      moof: moof,
43962      moov: moov,
43963      initSegment: function initSegment(tracks) {
43964        var fileType = ftyp(),
43965            movie = moov(tracks),
43966            result;
43967        result = new Uint8Array(fileType.byteLength + movie.byteLength);
43968        result.set(fileType);
43969        result.set(movie, fileType.byteLength);
43970        return result;
43971      }
43972    };
43973    /**
43974     * mux.js
43975     *
43976     * Copyright (c) Brightcove
43977     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43978     */
43979    // Convert an array of nal units into an array of frames with each frame being
43980    // composed of the nal units that make up that frame
43981    // Also keep track of cummulative data about the frame from the nal units such
43982    // as the frame duration, starting pts, etc.
43983
43984    var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
43985      var i,
43986          currentNal,
43987          currentFrame = [],
43988          frames = []; // TODO added for LHLS, make sure this is OK
43989
43990      frames.byteLength = 0;
43991      frames.nalCount = 0;
43992      frames.duration = 0;
43993      currentFrame.byteLength = 0;
43994
43995      for (i = 0; i < nalUnits.length; i++) {
43996        currentNal = nalUnits[i]; // Split on 'aud'-type nal units
43997
43998        if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
43999          // Since the very first nal unit is expected to be an AUD
44000          // only push to the frames array when currentFrame is not empty
44001          if (currentFrame.length) {
44002            currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
44003
44004            frames.byteLength += currentFrame.byteLength;
44005            frames.nalCount += currentFrame.length;
44006            frames.duration += currentFrame.duration;
44007            frames.push(currentFrame);
44008          }
44009
44010          currentFrame = [currentNal];
44011          currentFrame.byteLength = currentNal.data.byteLength;
44012          currentFrame.pts = currentNal.pts;
44013          currentFrame.dts = currentNal.dts;
44014        } else {
44015          // Specifically flag key frames for ease of use later
44016          if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
44017            currentFrame.keyFrame = true;
44018          }
44019
44020          currentFrame.duration = currentNal.dts - currentFrame.dts;
44021          currentFrame.byteLength += currentNal.data.byteLength;
44022          currentFrame.push(currentNal);
44023        }
44024      } // For the last frame, use the duration of the previous frame if we
44025      // have nothing better to go on
44026
44027
44028      if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
44029        currentFrame.duration = frames[frames.length - 1].duration;
44030      } // Push the final frame
44031      // TODO added for LHLS, make sure this is OK
44032
44033
44034      frames.byteLength += currentFrame.byteLength;
44035      frames.nalCount += currentFrame.length;
44036      frames.duration += currentFrame.duration;
44037      frames.push(currentFrame);
44038      return frames;
44039    }; // Convert an array of frames into an array of Gop with each Gop being composed
44040    // of the frames that make up that Gop
44041    // Also keep track of cummulative data about the Gop from the frames such as the
44042    // Gop duration, starting pts, etc.
44043
44044
44045    var groupFramesIntoGops = function groupFramesIntoGops(frames) {
44046      var i,
44047          currentFrame,
44048          currentGop = [],
44049          gops = []; // We must pre-set some of the values on the Gop since we
44050      // keep running totals of these values
44051
44052      currentGop.byteLength = 0;
44053      currentGop.nalCount = 0;
44054      currentGop.duration = 0;
44055      currentGop.pts = frames[0].pts;
44056      currentGop.dts = frames[0].dts; // store some metadata about all the Gops
44057
44058      gops.byteLength = 0;
44059      gops.nalCount = 0;
44060      gops.duration = 0;
44061      gops.pts = frames[0].pts;
44062      gops.dts = frames[0].dts;
44063
44064      for (i = 0; i < frames.length; i++) {
44065        currentFrame = frames[i];
44066
44067        if (currentFrame.keyFrame) {
44068          // Since the very first frame is expected to be an keyframe
44069          // only push to the gops array when currentGop is not empty
44070          if (currentGop.length) {
44071            gops.push(currentGop);
44072            gops.byteLength += currentGop.byteLength;
44073            gops.nalCount += currentGop.nalCount;
44074            gops.duration += currentGop.duration;
44075          }
44076
44077          currentGop = [currentFrame];
44078          currentGop.nalCount = currentFrame.length;
44079          currentGop.byteLength = currentFrame.byteLength;
44080          currentGop.pts = currentFrame.pts;
44081          currentGop.dts = currentFrame.dts;
44082          currentGop.duration = currentFrame.duration;
44083        } else {
44084          currentGop.duration += currentFrame.duration;
44085          currentGop.nalCount += currentFrame.length;
44086          currentGop.byteLength += currentFrame.byteLength;
44087          currentGop.push(currentFrame);
44088        }
44089      }
44090
44091      if (gops.length && currentGop.duration <= 0) {
44092        currentGop.duration = gops[gops.length - 1].duration;
44093      }
44094
44095      gops.byteLength += currentGop.byteLength;
44096      gops.nalCount += currentGop.nalCount;
44097      gops.duration += currentGop.duration; // push the final Gop
44098
44099      gops.push(currentGop);
44100      return gops;
44101    };
44102    /*
44103     * Search for the first keyframe in the GOPs and throw away all frames
44104     * until that keyframe. Then extend the duration of the pulled keyframe
44105     * and pull the PTS and DTS of the keyframe so that it covers the time
44106     * range of the frames that were disposed.
44107     *
44108     * @param {Array} gops video GOPs
44109     * @returns {Array} modified video GOPs
44110     */
44111
44112
44113    var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
44114      var currentGop;
44115
44116      if (!gops[0][0].keyFrame && gops.length > 1) {
44117        // Remove the first GOP
44118        currentGop = gops.shift();
44119        gops.byteLength -= currentGop.byteLength;
44120        gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
44121        // first gop to cover the time period of the
44122        // frames we just removed
44123
44124        gops[0][0].dts = currentGop.dts;
44125        gops[0][0].pts = currentGop.pts;
44126        gops[0][0].duration += currentGop.duration;
44127      }
44128
44129      return gops;
44130    };
44131    /**
44132     * Default sample object
44133     * see ISO/IEC 14496-12:2012, section 8.6.4.3
44134     */
44135
44136
44137    var createDefaultSample = function createDefaultSample() {
44138      return {
44139        size: 0,
44140        flags: {
44141          isLeading: 0,
44142          dependsOn: 1,
44143          isDependedOn: 0,
44144          hasRedundancy: 0,
44145          degradationPriority: 0,
44146          isNonSyncSample: 1
44147        }
44148      };
44149    };
44150    /*
44151     * Collates information from a video frame into an object for eventual
44152     * entry into an MP4 sample table.
44153     *
44154     * @param {Object} frame the video frame
44155     * @param {Number} dataOffset the byte offset to position the sample
44156     * @return {Object} object containing sample table info for a frame
44157     */
44158
44159
44160    var sampleForFrame = function sampleForFrame(frame, dataOffset) {
44161      var sample = createDefaultSample();
44162      sample.dataOffset = dataOffset;
44163      sample.compositionTimeOffset = frame.pts - frame.dts;
44164      sample.duration = frame.duration;
44165      sample.size = 4 * frame.length; // Space for nal unit size
44166
44167      sample.size += frame.byteLength;
44168
44169      if (frame.keyFrame) {
44170        sample.flags.dependsOn = 2;
44171        sample.flags.isNonSyncSample = 0;
44172      }
44173
44174      return sample;
44175    }; // generate the track's sample table from an array of gops
44176
44177
44178    var generateSampleTable$1 = function generateSampleTable(gops, baseDataOffset) {
44179      var h,
44180          i,
44181          sample,
44182          currentGop,
44183          currentFrame,
44184          dataOffset = baseDataOffset || 0,
44185          samples = [];
44186
44187      for (h = 0; h < gops.length; h++) {
44188        currentGop = gops[h];
44189
44190        for (i = 0; i < currentGop.length; i++) {
44191          currentFrame = currentGop[i];
44192          sample = sampleForFrame(currentFrame, dataOffset);
44193          dataOffset += sample.size;
44194          samples.push(sample);
44195        }
44196      }
44197
44198      return samples;
44199    }; // generate the track's raw mdat data from an array of gops
44200
44201
44202    var concatenateNalData = function concatenateNalData(gops) {
44203      var h,
44204          i,
44205          j,
44206          currentGop,
44207          currentFrame,
44208          currentNal,
44209          dataOffset = 0,
44210          nalsByteLength = gops.byteLength,
44211          numberOfNals = gops.nalCount,
44212          totalByteLength = nalsByteLength + 4 * numberOfNals,
44213          data = new Uint8Array(totalByteLength),
44214          view = new DataView(data.buffer); // For each Gop..
44215
44216      for (h = 0; h < gops.length; h++) {
44217        currentGop = gops[h]; // For each Frame..
44218
44219        for (i = 0; i < currentGop.length; i++) {
44220          currentFrame = currentGop[i]; // For each NAL..
44221
44222          for (j = 0; j < currentFrame.length; j++) {
44223            currentNal = currentFrame[j];
44224            view.setUint32(dataOffset, currentNal.data.byteLength);
44225            dataOffset += 4;
44226            data.set(currentNal.data, dataOffset);
44227            dataOffset += currentNal.data.byteLength;
44228          }
44229        }
44230      }
44231
44232      return data;
44233    }; // generate the track's sample table from a frame
44234
44235
44236    var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
44237      var sample,
44238          dataOffset = baseDataOffset || 0,
44239          samples = [];
44240      sample = sampleForFrame(frame, dataOffset);
44241      samples.push(sample);
44242      return samples;
44243    }; // generate the track's raw mdat data from a frame
44244
44245
44246    var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
44247      var i,
44248          currentNal,
44249          dataOffset = 0,
44250          nalsByteLength = frame.byteLength,
44251          numberOfNals = frame.length,
44252          totalByteLength = nalsByteLength + 4 * numberOfNals,
44253          data = new Uint8Array(totalByteLength),
44254          view = new DataView(data.buffer); // For each NAL..
44255
44256      for (i = 0; i < frame.length; i++) {
44257        currentNal = frame[i];
44258        view.setUint32(dataOffset, currentNal.data.byteLength);
44259        dataOffset += 4;
44260        data.set(currentNal.data, dataOffset);
44261        dataOffset += currentNal.data.byteLength;
44262      }
44263
44264      return data;
44265    };
44266
44267    var frameUtils = {
44268      groupNalsIntoFrames: groupNalsIntoFrames,
44269      groupFramesIntoGops: groupFramesIntoGops,
44270      extendFirstKeyFrame: extendFirstKeyFrame,
44271      generateSampleTable: generateSampleTable$1,
44272      concatenateNalData: concatenateNalData,
44273      generateSampleTableForFrame: generateSampleTableForFrame,
44274      concatenateNalDataForFrame: concatenateNalDataForFrame
44275    };
44276    /**
44277     * mux.js
44278     *
44279     * Copyright (c) Brightcove
44280     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44281     */
44282
44283    var highPrefix = [33, 16, 5, 32, 164, 27];
44284    var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
44285
44286    var zeroFill = function zeroFill(count) {
44287      var a = [];
44288
44289      while (count--) {
44290        a.push(0);
44291      }
44292
44293      return a;
44294    };
44295
44296    var makeTable = function makeTable(metaTable) {
44297      return Object.keys(metaTable).reduce(function (obj, key) {
44298        obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
44299          return arr.concat(part);
44300        }, []));
44301        return obj;
44302      }, {});
44303    };
44304
44305    var silence;
44306
44307    var silence_1 = function silence_1() {
44308      if (!silence) {
44309        // Frames-of-silence to use for filling in missing AAC frames
44310        var coneOfSilence = {
44311          96000: [highPrefix, [227, 64], zeroFill(154), [56]],
44312          88200: [highPrefix, [231], zeroFill(170), [56]],
44313          64000: [highPrefix, [248, 192], zeroFill(240), [56]],
44314          48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
44315          44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
44316          32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
44317          24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
44318          16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
44319          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]],
44320          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]],
44321          8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
44322        };
44323        silence = makeTable(coneOfSilence);
44324      }
44325
44326      return silence;
44327    };
44328    /**
44329     * mux.js
44330     *
44331     * Copyright (c) Brightcove
44332     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44333     */
44334
44335
44336    var ONE_SECOND_IN_TS$4 = 90000,
44337        // 90kHz clock
44338    secondsToVideoTs,
44339        secondsToAudioTs,
44340        videoTsToSeconds,
44341        audioTsToSeconds,
44342        audioTsToVideoTs,
44343        videoTsToAudioTs,
44344        metadataTsToSeconds;
44345
44346    secondsToVideoTs = function secondsToVideoTs(seconds) {
44347      return seconds * ONE_SECOND_IN_TS$4;
44348    };
44349
44350    secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
44351      return seconds * sampleRate;
44352    };
44353
44354    videoTsToSeconds = function videoTsToSeconds(timestamp) {
44355      return timestamp / ONE_SECOND_IN_TS$4;
44356    };
44357
44358    audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
44359      return timestamp / sampleRate;
44360    };
44361
44362    audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
44363      return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
44364    };
44365
44366    videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
44367      return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
44368    };
44369    /**
44370     * Adjust ID3 tag or caption timing information by the timeline pts values
44371     * (if keepOriginalTimestamps is false) and convert to seconds
44372     */
44373
44374
44375    metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
44376      return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
44377    };
44378
44379    var clock = {
44380      ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
44381      secondsToVideoTs: secondsToVideoTs,
44382      secondsToAudioTs: secondsToAudioTs,
44383      videoTsToSeconds: videoTsToSeconds,
44384      audioTsToSeconds: audioTsToSeconds,
44385      audioTsToVideoTs: audioTsToVideoTs,
44386      videoTsToAudioTs: videoTsToAudioTs,
44387      metadataTsToSeconds: metadataTsToSeconds
44388    };
44389    /**
44390     * mux.js
44391     *
44392     * Copyright (c) Brightcove
44393     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44394     */
44395
44396    /**
44397     * Sum the `byteLength` properties of the data in each AAC frame
44398     */
44399
44400    var sumFrameByteLengths = function sumFrameByteLengths(array) {
44401      var i,
44402          currentObj,
44403          sum = 0; // sum the byteLength's all each nal unit in the frame
44404
44405      for (i = 0; i < array.length; i++) {
44406        currentObj = array[i];
44407        sum += currentObj.data.byteLength;
44408      }
44409
44410      return sum;
44411    }; // Possibly pad (prefix) the audio track with silence if appending this track
44412    // would lead to the introduction of a gap in the audio buffer
44413
44414
44415    var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
44416      var baseMediaDecodeTimeTs,
44417          frameDuration = 0,
44418          audioGapDuration = 0,
44419          audioFillFrameCount = 0,
44420          audioFillDuration = 0,
44421          silentFrame,
44422          i,
44423          firstFrame;
44424
44425      if (!frames.length) {
44426        return;
44427      }
44428
44429      baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
44430
44431      frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
44432
44433      if (audioAppendStartTs && videoBaseMediaDecodeTime) {
44434        // insert the shortest possible amount (audio gap or audio to video gap)
44435        audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
44436
44437        audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
44438        audioFillDuration = audioFillFrameCount * frameDuration;
44439      } // don't attempt to fill gaps smaller than a single frame or larger
44440      // than a half second
44441
44442
44443      if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
44444        return;
44445      }
44446
44447      silentFrame = silence_1()[track.samplerate];
44448
44449      if (!silentFrame) {
44450        // we don't have a silent frame pregenerated for the sample rate, so use a frame
44451        // from the content instead
44452        silentFrame = frames[0].data;
44453      }
44454
44455      for (i = 0; i < audioFillFrameCount; i++) {
44456        firstFrame = frames[0];
44457        frames.splice(0, 0, {
44458          data: silentFrame,
44459          dts: firstFrame.dts - frameDuration,
44460          pts: firstFrame.pts - frameDuration
44461        });
44462      }
44463
44464      track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
44465      return audioFillDuration;
44466    }; // If the audio segment extends before the earliest allowed dts
44467    // value, remove AAC frames until starts at or after the earliest
44468    // allowed DTS so that we don't end up with a negative baseMedia-
44469    // DecodeTime for the audio track
44470
44471
44472    var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
44473      if (track.minSegmentDts >= earliestAllowedDts) {
44474        return adtsFrames;
44475      } // We will need to recalculate the earliest segment Dts
44476
44477
44478      track.minSegmentDts = Infinity;
44479      return adtsFrames.filter(function (currentFrame) {
44480        // If this is an allowed frame, keep it and record it's Dts
44481        if (currentFrame.dts >= earliestAllowedDts) {
44482          track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
44483          track.minSegmentPts = track.minSegmentDts;
44484          return true;
44485        } // Otherwise, discard it
44486
44487
44488        return false;
44489      });
44490    }; // generate the track's raw mdat data from an array of frames
44491
44492
44493    var generateSampleTable = function generateSampleTable(frames) {
44494      var i,
44495          currentFrame,
44496          samples = [];
44497
44498      for (i = 0; i < frames.length; i++) {
44499        currentFrame = frames[i];
44500        samples.push({
44501          size: currentFrame.data.byteLength,
44502          duration: 1024 // For AAC audio, all samples contain 1024 samples
44503
44504        });
44505      }
44506
44507      return samples;
44508    }; // generate the track's sample table from an array of frames
44509
44510
44511    var concatenateFrameData = function concatenateFrameData(frames) {
44512      var i,
44513          currentFrame,
44514          dataOffset = 0,
44515          data = new Uint8Array(sumFrameByteLengths(frames));
44516
44517      for (i = 0; i < frames.length; i++) {
44518        currentFrame = frames[i];
44519        data.set(currentFrame.data, dataOffset);
44520        dataOffset += currentFrame.data.byteLength;
44521      }
44522
44523      return data;
44524    };
44525
44526    var audioFrameUtils = {
44527      prefixWithSilence: prefixWithSilence,
44528      trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
44529      generateSampleTable: generateSampleTable,
44530      concatenateFrameData: concatenateFrameData
44531    };
44532    /**
44533     * mux.js
44534     *
44535     * Copyright (c) Brightcove
44536     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44537     */
44538
44539    var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS;
44540    /**
44541     * Store information about the start and end of the track and the
44542     * duration for each frame/sample we process in order to calculate
44543     * the baseMediaDecodeTime
44544     */
44545
44546    var collectDtsInfo = function collectDtsInfo(track, data) {
44547      if (typeof data.pts === 'number') {
44548        if (track.timelineStartInfo.pts === undefined) {
44549          track.timelineStartInfo.pts = data.pts;
44550        }
44551
44552        if (track.minSegmentPts === undefined) {
44553          track.minSegmentPts = data.pts;
44554        } else {
44555          track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
44556        }
44557
44558        if (track.maxSegmentPts === undefined) {
44559          track.maxSegmentPts = data.pts;
44560        } else {
44561          track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
44562        }
44563      }
44564
44565      if (typeof data.dts === 'number') {
44566        if (track.timelineStartInfo.dts === undefined) {
44567          track.timelineStartInfo.dts = data.dts;
44568        }
44569
44570        if (track.minSegmentDts === undefined) {
44571          track.minSegmentDts = data.dts;
44572        } else {
44573          track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
44574        }
44575
44576        if (track.maxSegmentDts === undefined) {
44577          track.maxSegmentDts = data.dts;
44578        } else {
44579          track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
44580        }
44581      }
44582    };
44583    /**
44584     * Clear values used to calculate the baseMediaDecodeTime between
44585     * tracks
44586     */
44587
44588
44589    var clearDtsInfo = function clearDtsInfo(track) {
44590      delete track.minSegmentDts;
44591      delete track.maxSegmentDts;
44592      delete track.minSegmentPts;
44593      delete track.maxSegmentPts;
44594    };
44595    /**
44596     * Calculate the track's baseMediaDecodeTime based on the earliest
44597     * DTS the transmuxer has ever seen and the minimum DTS for the
44598     * current track
44599     * @param track {object} track metadata configuration
44600     * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
44601     *        in the source; false to adjust the first segment to start at 0.
44602     */
44603
44604
44605    var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
44606      var baseMediaDecodeTime,
44607          scale,
44608          minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
44609
44610      if (!keepOriginalTimestamps) {
44611        minSegmentDts -= track.timelineStartInfo.dts;
44612      } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
44613      // we want the start of the first segment to be placed
44614
44615
44616      baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
44617
44618      baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
44619
44620      baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
44621
44622      if (track.type === 'audio') {
44623        // Audio has a different clock equal to the sampling_rate so we need to
44624        // scale the PTS values into the clock rate of the track
44625        scale = track.samplerate / ONE_SECOND_IN_TS$3;
44626        baseMediaDecodeTime *= scale;
44627        baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
44628      }
44629
44630      return baseMediaDecodeTime;
44631    };
44632
44633    var trackDecodeInfo = {
44634      clearDtsInfo: clearDtsInfo,
44635      calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
44636      collectDtsInfo: collectDtsInfo
44637    };
44638    /**
44639     * mux.js
44640     *
44641     * Copyright (c) Brightcove
44642     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44643     *
44644     * Reads in-band caption information from a video elementary
44645     * stream. Captions must follow the CEA-708 standard for injection
44646     * into an MPEG-2 transport streams.
44647     * @see https://en.wikipedia.org/wiki/CEA-708
44648     * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
44649     */
44650    // payload type field to indicate how they are to be
44651    // interpreted. CEAS-708 caption content is always transmitted with
44652    // payload type 0x04.
44653
44654    var USER_DATA_REGISTERED_ITU_T_T35 = 4,
44655        RBSP_TRAILING_BITS = 128;
44656    /**
44657      * Parse a supplemental enhancement information (SEI) NAL unit.
44658      * Stops parsing once a message of type ITU T T35 has been found.
44659      *
44660      * @param bytes {Uint8Array} the bytes of a SEI NAL unit
44661      * @return {object} the parsed SEI payload
44662      * @see Rec. ITU-T H.264, 7.3.2.3.1
44663      */
44664
44665    var parseSei = function parseSei(bytes) {
44666      var i = 0,
44667          result = {
44668        payloadType: -1,
44669        payloadSize: 0
44670      },
44671          payloadType = 0,
44672          payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
44673
44674      while (i < bytes.byteLength) {
44675        // stop once we have hit the end of the sei_rbsp
44676        if (bytes[i] === RBSP_TRAILING_BITS) {
44677          break;
44678        } // Parse payload type
44679
44680
44681        while (bytes[i] === 0xFF) {
44682          payloadType += 255;
44683          i++;
44684        }
44685
44686        payloadType += bytes[i++]; // Parse payload size
44687
44688        while (bytes[i] === 0xFF) {
44689          payloadSize += 255;
44690          i++;
44691        }
44692
44693        payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
44694        // there can only ever be one caption message in a frame's sei
44695
44696        if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
44697          var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
44698
44699          if (userIdentifier === 'GA94') {
44700            result.payloadType = payloadType;
44701            result.payloadSize = payloadSize;
44702            result.payload = bytes.subarray(i, i + payloadSize);
44703            break;
44704          } else {
44705            result.payload = void 0;
44706          }
44707        } // skip the payload and parse the next message
44708
44709
44710        i += payloadSize;
44711        payloadType = 0;
44712        payloadSize = 0;
44713      }
44714
44715      return result;
44716    }; // see ANSI/SCTE 128-1 (2013), section 8.1
44717
44718
44719    var parseUserData = function parseUserData(sei) {
44720      // itu_t_t35_contry_code must be 181 (United States) for
44721      // captions
44722      if (sei.payload[0] !== 181) {
44723        return null;
44724      } // itu_t_t35_provider_code should be 49 (ATSC) for captions
44725
44726
44727      if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
44728        return null;
44729      } // the user_identifier should be "GA94" to indicate ATSC1 data
44730
44731
44732      if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
44733        return null;
44734      } // finally, user_data_type_code should be 0x03 for caption data
44735
44736
44737      if (sei.payload[7] !== 0x03) {
44738        return null;
44739      } // return the user_data_type_structure and strip the trailing
44740      // marker bits
44741
44742
44743      return sei.payload.subarray(8, sei.payload.length - 1);
44744    }; // see CEA-708-D, section 4.4
44745
44746
44747    var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
44748      var results = [],
44749          i,
44750          count,
44751          offset,
44752          data; // if this is just filler, return immediately
44753
44754      if (!(userData[0] & 0x40)) {
44755        return results;
44756      } // parse out the cc_data_1 and cc_data_2 fields
44757
44758
44759      count = userData[0] & 0x1f;
44760
44761      for (i = 0; i < count; i++) {
44762        offset = i * 3;
44763        data = {
44764          type: userData[offset + 2] & 0x03,
44765          pts: pts
44766        }; // capture cc data when cc_valid is 1
44767
44768        if (userData[offset + 2] & 0x04) {
44769          data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
44770          results.push(data);
44771        }
44772      }
44773
44774      return results;
44775    };
44776
44777    var discardEmulationPreventionBytes$1 = function discardEmulationPreventionBytes(data) {
44778      var length = data.byteLength,
44779          emulationPreventionBytesPositions = [],
44780          i = 1,
44781          newLength,
44782          newData; // Find all `Emulation Prevention Bytes`
44783
44784      while (i < length - 2) {
44785        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
44786          emulationPreventionBytesPositions.push(i + 2);
44787          i += 2;
44788        } else {
44789          i++;
44790        }
44791      } // If no Emulation Prevention Bytes were found just return the original
44792      // array
44793
44794
44795      if (emulationPreventionBytesPositions.length === 0) {
44796        return data;
44797      } // Create a new array to hold the NAL unit data
44798
44799
44800      newLength = length - emulationPreventionBytesPositions.length;
44801      newData = new Uint8Array(newLength);
44802      var sourceIndex = 0;
44803
44804      for (i = 0; i < newLength; sourceIndex++, i++) {
44805        if (sourceIndex === emulationPreventionBytesPositions[0]) {
44806          // Skip this byte
44807          sourceIndex++; // Remove this position index
44808
44809          emulationPreventionBytesPositions.shift();
44810        }
44811
44812        newData[i] = data[sourceIndex];
44813      }
44814
44815      return newData;
44816    }; // exports
44817
44818
44819    var captionPacketParser = {
44820      parseSei: parseSei,
44821      parseUserData: parseUserData,
44822      parseCaptionPackets: parseCaptionPackets,
44823      discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
44824      USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
44825    }; // Link To Transport
44826    // -----------------
44827
44828    var CaptionStream$1 = function CaptionStream(options) {
44829      options = options || {};
44830      CaptionStream.prototype.init.call(this); // parse708captions flag, default to true
44831
44832      this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
44833      this.captionPackets_ = [];
44834      this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
44835      new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
44836      new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
44837      new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
44838      ];
44839
44840      if (this.parse708captions_) {
44841        this.cc708Stream_ = new Cea708Stream(); // eslint-disable-line no-use-before-define
44842      }
44843
44844      this.reset(); // forward data and done events from CCs to this CaptionStream
44845
44846      this.ccStreams_.forEach(function (cc) {
44847        cc.on('data', this.trigger.bind(this, 'data'));
44848        cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
44849        cc.on('done', this.trigger.bind(this, 'done'));
44850      }, this);
44851
44852      if (this.parse708captions_) {
44853        this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
44854        this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
44855        this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
44856      }
44857    };
44858
44859    CaptionStream$1.prototype = new stream();
44860
44861    CaptionStream$1.prototype.push = function (event) {
44862      var sei, userData, newCaptionPackets; // only examine SEI NALs
44863
44864      if (event.nalUnitType !== 'sei_rbsp') {
44865        return;
44866      } // parse the sei
44867
44868
44869      sei = captionPacketParser.parseSei(event.escapedRBSP); // no payload data, skip
44870
44871      if (!sei.payload) {
44872        return;
44873      } // ignore everything but user_data_registered_itu_t_t35
44874
44875
44876      if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
44877        return;
44878      } // parse out the user data payload
44879
44880
44881      userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
44882
44883      if (!userData) {
44884        return;
44885      } // Sometimes, the same segment # will be downloaded twice. To stop the
44886      // caption data from being processed twice, we track the latest dts we've
44887      // received and ignore everything with a dts before that. However, since
44888      // data for a specific dts can be split across packets on either side of
44889      // a segment boundary, we need to make sure we *don't* ignore the packets
44890      // from the *next* segment that have dts === this.latestDts_. By constantly
44891      // tracking the number of packets received with dts === this.latestDts_, we
44892      // know how many should be ignored once we start receiving duplicates.
44893
44894
44895      if (event.dts < this.latestDts_) {
44896        // We've started getting older data, so set the flag.
44897        this.ignoreNextEqualDts_ = true;
44898        return;
44899      } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
44900        this.numSameDts_--;
44901
44902        if (!this.numSameDts_) {
44903          // We've received the last duplicate packet, time to start processing again
44904          this.ignoreNextEqualDts_ = false;
44905        }
44906
44907        return;
44908      } // parse out CC data packets and save them for later
44909
44910
44911      newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
44912      this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
44913
44914      if (this.latestDts_ !== event.dts) {
44915        this.numSameDts_ = 0;
44916      }
44917
44918      this.numSameDts_++;
44919      this.latestDts_ = event.dts;
44920    };
44921
vendor: 5,500 bytes, lines 44922-45062
44922    CaptionStream$1.prototype.flushCCStreams = function (flushType) {
44923      this.ccStreams_.forEach(function (cc) {
44924        return flushType === 'flush' ? cc.flush() : cc.partialFlush();
44925      }, this);
44926    };
44927
44928    CaptionStream$1.prototype.flushStream = function (flushType) {
44929      // make sure we actually parsed captions before proceeding
44930      if (!this.captionPackets_.length) {
44931        this.flushCCStreams(flushType);
44932        return;
44933      } // In Chrome, the Array#sort function is not stable so add a
44934      // presortIndex that we can use to ensure we get a stable-sort
44935
44936
44937      this.captionPackets_.forEach(function (elem, idx) {
44938        elem.presortIndex = idx;
44939      }); // sort caption byte-pairs based on their PTS values
44940
44941      this.captionPackets_.sort(function (a, b) {
44942        if (a.pts === b.pts) {
44943          return a.presortIndex - b.presortIndex;
44944        }
44945
44946        return a.pts - b.pts;
44947      });
44948      this.captionPackets_.forEach(function (packet) {
44949        if (packet.type < 2) {
44950          // Dispatch packet to the right Cea608Stream
44951          this.dispatchCea608Packet(packet);
44952        } else {
44953          // Dispatch packet to the Cea708Stream
44954          this.dispatchCea708Packet(packet);
44955        }
44956      }, this);
44957      this.captionPackets_.length = 0;
44958      this.flushCCStreams(flushType);
44959    };
44960
44961    CaptionStream$1.prototype.flush = function () {
44962      return this.flushStream('flush');
44963    }; // Only called if handling partial data
44964
44965
44966    CaptionStream$1.prototype.partialFlush = function () {
44967      return this.flushStream('partialFlush');
44968    };
44969
44970    CaptionStream$1.prototype.reset = function () {
44971      this.latestDts_ = null;
44972      this.ignoreNextEqualDts_ = false;
44973      this.numSameDts_ = 0;
44974      this.activeCea608Channel_ = [null, null];
44975      this.ccStreams_.forEach(function (ccStream) {
44976        ccStream.reset();
44977      });
44978    }; // From the CEA-608 spec:
44979
44980    /*
44981     * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
44982     * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
44983     * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
44984     * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
44985     * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
44986     * to switch to captioning or Text.
44987    */
44988    // With that in mind, we ignore any data between an XDS control code and a
44989    // subsequent closed-captioning control code.
44990
44991
44992    CaptionStream$1.prototype.dispatchCea608Packet = function (packet) {
44993      // NOTE: packet.type is the CEA608 field
44994      if (this.setsTextOrXDSActive(packet)) {
44995        this.activeCea608Channel_[packet.type] = null;
44996      } else if (this.setsChannel1Active(packet)) {
44997        this.activeCea608Channel_[packet.type] = 0;
44998      } else if (this.setsChannel2Active(packet)) {
44999        this.activeCea608Channel_[packet.type] = 1;
45000      }
45001
45002      if (this.activeCea608Channel_[packet.type] === null) {
45003        // If we haven't received anything to set the active channel, or the
45004        // packets are Text/XDS data, discard the data; we don't want jumbled
45005        // captions
45006        return;
45007      }
45008
45009      this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
45010    };
45011
45012    CaptionStream$1.prototype.setsChannel1Active = function (packet) {
45013      return (packet.ccData & 0x7800) === 0x1000;
45014    };
45015
45016    CaptionStream$1.prototype.setsChannel2Active = function (packet) {
45017      return (packet.ccData & 0x7800) === 0x1800;
45018    };
45019
45020    CaptionStream$1.prototype.setsTextOrXDSActive = function (packet) {
45021      return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
45022    };
45023
45024    CaptionStream$1.prototype.dispatchCea708Packet = function (packet) {
45025      if (this.parse708captions_) {
45026        this.cc708Stream_.push(packet);
45027      }
45028    }; // ----------------------
45029    // Session to Application
45030    // ----------------------
45031    // This hash maps special and extended character codes to their
45032    // proper Unicode equivalent. The first one-byte key is just a
45033    // non-standard character code. The two-byte keys that follow are
45034    // the extended CEA708 character codes, along with the preceding
45035    // 0x10 extended character byte to distinguish these codes from
45036    // non-extended character codes. Every CEA708 character code that
45037    // is not in this object maps directly to a standard unicode
45038    // character code.
45039    // The transparent space and non-breaking transparent space are
45040    // technically not fully supported since there is no code to
45041    // make them transparent, so they have normal non-transparent
45042    // stand-ins.
45043    // The special closed caption (CC) character isn't a standard
45044    // unicode character, so a fairly similar unicode character was
45045    // chosen in it's place.
45046
45047
45048    var CHARACTER_TRANSLATION_708 = {
45049      0x7f: 0x266a,
45050      // ♪
45051      0x1020: 0x20,
45052      // Transparent Space
45053      0x1021: 0xa0,
45054      // Nob-breaking Transparent Space
45055      0x1025: 0x2026,
45056      // …
45057      0x102a: 0x0160,
45058      // Å 
45059      0x102c: 0x0152,
45060      // Œ
45061      0x1030: 0x2588,
45062      // █
vendor: 14,361 bytes, lines 45063-45552
45063      0x1031: 0x2018,
45064      // ‘
45065      0x1032: 0x2019,
45066      // ’
45067      0x1033: 0x201c,
45068      // “
45069      0x1034: 0x201d,
45070      // ”
45071      0x1035: 0x2022,
45072      // •
45073      0x1039: 0x2122,
45074      // ™
45075      0x103a: 0x0161,
45076      // Å¡
45077      0x103c: 0x0153,
45078      // œ
45079      0x103d: 0x2120,
45080      // ℠
45081      0x103f: 0x0178,
45082      // Ÿ
45083      0x1076: 0x215b,
45084      // ⅛
45085      0x1077: 0x215c,
45086      // ⅜
45087      0x1078: 0x215d,
45088      // ⅝
45089      0x1079: 0x215e,
45090      // ⅞
45091      0x107a: 0x23d0,
45092      // ⏐
45093      0x107b: 0x23a4,
45094      // ⎤
45095      0x107c: 0x23a3,
45096      // ⎣
45097      0x107d: 0x23af,
45098      // ⎯
45099      0x107e: 0x23a6,
45100      // ⎦
45101      0x107f: 0x23a1,
45102      // ⎡
45103      0x10a0: 0x3138 // ㄸ (CC char)
45104
45105    };
45106
45107    var get708CharFromCode = function get708CharFromCode(code) {
45108      var newCode = CHARACTER_TRANSLATION_708[code] || code;
45109
45110      if (code & 0x1000 && code === newCode) {
45111        // Invalid extended code
45112        return '';
45113      }
45114
45115      return String.fromCharCode(newCode);
45116    };
45117
45118    var within708TextBlock = function within708TextBlock(b) {
45119      return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
45120    };
45121
45122    var Cea708Window = function Cea708Window(windowNum) {
45123      this.windowNum = windowNum;
45124      this.reset();
45125    };
45126
45127    Cea708Window.prototype.reset = function () {
45128      this.clearText();
45129      this.pendingNewLine = false;
45130      this.winAttr = {};
45131      this.penAttr = {};
45132      this.penLoc = {};
45133      this.penColor = {}; // These default values are arbitrary,
45134      // defineWindow will usually override them
45135
45136      this.visible = 0;
45137      this.rowLock = 0;
45138      this.columnLock = 0;
45139      this.priority = 0;
45140      this.relativePositioning = 0;
45141      this.anchorVertical = 0;
45142      this.anchorHorizontal = 0;
45143      this.anchorPoint = 0;
45144      this.rowCount = 1;
45145      this.virtualRowCount = this.rowCount + 1;
45146      this.columnCount = 41;
45147      this.windowStyle = 0;
45148      this.penStyle = 0;
45149    };
45150
45151    Cea708Window.prototype.getText = function () {
45152      return this.rows.join('\n');
45153    };
45154
45155    Cea708Window.prototype.clearText = function () {
45156      this.rows = [''];
45157      this.rowIdx = 0;
45158    };
45159
45160    Cea708Window.prototype.newLine = function (pts) {
45161      if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
45162        this.beforeRowOverflow(pts);
45163      }
45164
45165      if (this.rows.length > 0) {
45166        this.rows.push('');
45167        this.rowIdx++;
45168      } // Show all virtual rows since there's no visible scrolling
45169
45170
45171      while (this.rows.length > this.virtualRowCount) {
45172        this.rows.shift();
45173        this.rowIdx--;
45174      }
45175    };
45176
45177    Cea708Window.prototype.isEmpty = function () {
45178      if (this.rows.length === 0) {
45179        return true;
45180      } else if (this.rows.length === 1) {
45181        return this.rows[0] === '';
45182      }
45183
45184      return false;
45185    };
45186
45187    Cea708Window.prototype.addText = function (text) {
45188      this.rows[this.rowIdx] += text;
45189    };
45190
45191    Cea708Window.prototype.backspace = function () {
45192      if (!this.isEmpty()) {
45193        var row = this.rows[this.rowIdx];
45194        this.rows[this.rowIdx] = row.substr(0, row.length - 1);
45195      }
45196    };
45197
45198    var Cea708Service = function Cea708Service(serviceNum) {
45199      this.serviceNum = serviceNum;
45200      this.text = '';
45201      this.currentWindow = new Cea708Window(-1);
45202      this.windows = [];
45203    };
45204    /**
45205     * Initialize service windows
45206     * Must be run before service use
45207     *
45208     * @param  {Integer}  pts               PTS value
45209     * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
45210     */
45211
45212
45213    Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
45214      this.startPts = pts;
45215
45216      for (var win = 0; win < 8; win++) {
45217        this.windows[win] = new Cea708Window(win);
45218
45219        if (typeof beforeRowOverflow === 'function') {
45220          this.windows[win].beforeRowOverflow = beforeRowOverflow;
45221        }
45222      }
45223    };
45224    /**
45225     * Set current window of service to be affected by commands
45226     *
45227     * @param  {Integer} windowNum Window number
45228     */
45229
45230
45231    Cea708Service.prototype.setCurrentWindow = function (windowNum) {
45232      this.currentWindow = this.windows[windowNum];
45233    };
45234
45235    var Cea708Stream = function Cea708Stream() {
45236      Cea708Stream.prototype.init.call(this);
45237      var self = this;
45238      this.current708Packet = null;
45239      this.services = {};
45240
45241      this.push = function (packet) {
45242        if (packet.type === 3) {
45243          // 708 packet start
45244          self.new708Packet();
45245          self.add708Bytes(packet);
45246        } else {
45247          if (self.current708Packet === null) {
45248            // This should only happen at the start of a file if there's no packet start.
45249            self.new708Packet();
45250          }
45251
45252          self.add708Bytes(packet);
45253        }
45254      };
45255    };
45256
45257    Cea708Stream.prototype = new stream();
45258    /**
45259     * Push current 708 packet, create new 708 packet.
45260     */
45261
45262    Cea708Stream.prototype.new708Packet = function () {
45263      if (this.current708Packet !== null) {
45264        this.push708Packet();
45265      }
45266
45267      this.current708Packet = {
45268        data: [],
45269        ptsVals: []
45270      };
45271    };
45272    /**
45273     * Add pts and both bytes from packet into current 708 packet.
45274     */
45275
45276
45277    Cea708Stream.prototype.add708Bytes = function (packet) {
45278      var data = packet.ccData;
45279      var byte0 = data >>> 8;
45280      var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
45281      // that service blocks will always line up with byte pairs.
45282
45283      this.current708Packet.ptsVals.push(packet.pts);
45284      this.current708Packet.data.push(byte0);
45285      this.current708Packet.data.push(byte1);
45286    };
45287    /**
45288     * Parse completed 708 packet into service blocks and push each service block.
45289     */
45290
45291
45292    Cea708Stream.prototype.push708Packet = function () {
45293      var packet708 = this.current708Packet;
45294      var packetData = packet708.data;
45295      var serviceNum = null;
45296      var blockSize = null;
45297      var i = 0;
45298      var b = packetData[i++];
45299      packet708.seq = b >> 6;
45300      packet708.sizeCode = b & 0x3f; // 0b00111111;
45301
45302      for (; i < packetData.length; i++) {
45303        b = packetData[i++];
45304        serviceNum = b >> 5;
45305        blockSize = b & 0x1f; // 0b00011111
45306
45307        if (serviceNum === 7 && blockSize > 0) {
45308          // Extended service num
45309          b = packetData[i++];
45310          serviceNum = b;
45311        }
45312
45313        this.pushServiceBlock(serviceNum, i, blockSize);
45314
45315        if (blockSize > 0) {
45316          i += blockSize - 1;
45317        }
45318      }
45319    };
45320    /**
45321     * Parse service block, execute commands, read text.
45322     *
45323     * Note: While many of these commands serve important purposes,
45324     * many others just parse out the parameters or attributes, but
45325     * nothing is done with them because this is not a full and complete
45326     * implementation of the entire 708 spec.
45327     *
45328     * @param  {Integer} serviceNum Service number
45329     * @param  {Integer} start      Start index of the 708 packet data
45330     * @param  {Integer} size       Block size
45331     */
45332
45333
45334    Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
45335      var b;
45336      var i = start;
45337      var packetData = this.current708Packet.data;
45338      var service = this.services[serviceNum];
45339
45340      if (!service) {
45341        service = this.initService(serviceNum, i);
45342      }
45343
45344      for (; i < start + size && i < packetData.length; i++) {
45345        b = packetData[i];
45346
45347        if (within708TextBlock(b)) {
45348          i = this.handleText(i, service);
45349        } else if (b === 0x10) {
45350          i = this.extendedCommands(i, service);
45351        } else if (0x80 <= b && b <= 0x87) {
45352          i = this.setCurrentWindow(i, service);
45353        } else if (0x98 <= b && b <= 0x9f) {
45354          i = this.defineWindow(i, service);
45355        } else if (b === 0x88) {
45356          i = this.clearWindows(i, service);
45357        } else if (b === 0x8c) {
45358          i = this.deleteWindows(i, service);
45359        } else if (b === 0x89) {
45360          i = this.displayWindows(i, service);
45361        } else if (b === 0x8a) {
45362          i = this.hideWindows(i, service);
45363        } else if (b === 0x8b) {
45364          i = this.toggleWindows(i, service);
45365        } else if (b === 0x97) {
45366          i = this.setWindowAttributes(i, service);
45367        } else if (b === 0x90) {
45368          i = this.setPenAttributes(i, service);
45369        } else if (b === 0x91) {
45370          i = this.setPenColor(i, service);
45371        } else if (b === 0x92) {
45372          i = this.setPenLocation(i, service);
45373        } else if (b === 0x8f) {
45374          service = this.reset(i, service);
45375        } else if (b === 0x08) {
45376          // BS: Backspace
45377          service.currentWindow.backspace();
45378        } else if (b === 0x0c) {
45379          // FF: Form feed
45380          service.currentWindow.clearText();
45381        } else if (b === 0x0d) {
45382          // CR: Carriage return
45383          service.currentWindow.pendingNewLine = true;
45384        } else if (b === 0x0e) {
45385          // HCR: Horizontal carriage return
45386          service.currentWindow.clearText();
45387        } else if (b === 0x8d) {
45388          // DLY: Delay, nothing to do
45389          i++;
45390        } else ;
45391      }
45392    };
45393    /**
45394     * Execute an extended command
45395     *
45396     * @param  {Integer} i        Current index in the 708 packet
45397     * @param  {Service} service  The service object to be affected
45398     * @return {Integer}          New index after parsing
45399     */
45400
45401
45402    Cea708Stream.prototype.extendedCommands = function (i, service) {
45403      var packetData = this.current708Packet.data;
45404      var b = packetData[++i];
45405
45406      if (within708TextBlock(b)) {
45407        i = this.handleText(i, service, true);
45408      }
45409
45410      return i;
45411    };
45412    /**
45413     * Get PTS value of a given byte index
45414     *
45415     * @param  {Integer} byteIndex  Index of the byte
45416     * @return {Integer}            PTS
45417     */
45418
45419
45420    Cea708Stream.prototype.getPts = function (byteIndex) {
45421      // There's 1 pts value per 2 bytes
45422      return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
45423    };
45424    /**
45425     * Initializes a service
45426     *
45427     * @param  {Integer} serviceNum Service number
45428     * @return {Service}            Initialized service object
45429     */
45430
45431
45432    Cea708Stream.prototype.initService = function (serviceNum, i) {
45433      var self = this;
45434      this.services[serviceNum] = new Cea708Service(serviceNum);
45435      this.services[serviceNum].init(this.getPts(i), function (pts) {
45436        self.flushDisplayed(pts, self.services[serviceNum]);
45437      });
45438      return this.services[serviceNum];
45439    };
45440    /**
45441     * Execute text writing to current window
45442     *
45443     * @param  {Integer} i        Current index in the 708 packet
45444     * @param  {Service} service  The service object to be affected
45445     * @return {Integer}          New index after parsing
45446     */
45447
45448
45449    Cea708Stream.prototype.handleText = function (i, service, isExtended) {
45450      var packetData = this.current708Packet.data;
45451      var b = packetData[i];
45452      var extended = isExtended ? 0x1000 : 0x0000;
45453
45454      var _char = get708CharFromCode(extended | b);
45455
45456      var win = service.currentWindow;
45457
45458      if (win.pendingNewLine && !win.isEmpty()) {
45459        win.newLine(this.getPts(i));
45460      }
45461
45462      win.pendingNewLine = false;
45463      win.addText(_char);
45464      return i;
45465    };
45466    /**
45467     * Parse and execute the CW# command.
45468     *
45469     * Set the current window.
45470     *
45471     * @param  {Integer} i        Current index in the 708 packet
45472     * @param  {Service} service  The service object to be affected
45473     * @return {Integer}          New index after parsing
45474     */
45475
45476
45477    Cea708Stream.prototype.setCurrentWindow = function (i, service) {
45478      var packetData = this.current708Packet.data;
45479      var b = packetData[i];
45480      var windowNum = b & 0x07;
45481      service.setCurrentWindow(windowNum);
45482      return i;
45483    };
45484    /**
45485     * Parse and execute the DF# command.
45486     *
45487     * Define a window and set it as the current window.
45488     *
45489     * @param  {Integer} i        Current index in the 708 packet
45490     * @param  {Service} service  The service object to be affected
45491     * @return {Integer}          New index after parsing
45492     */
45493
45494
45495    Cea708Stream.prototype.defineWindow = function (i, service) {
45496      var packetData = this.current708Packet.data;
45497      var b = packetData[i];
45498      var windowNum = b & 0x07;
45499      service.setCurrentWindow(windowNum);
45500      var win = service.currentWindow;
45501      b = packetData[++i];
45502      win.visible = (b & 0x20) >> 5; // v
45503
45504      win.rowLock = (b & 0x10) >> 4; // rl
45505
45506      win.columnLock = (b & 0x08) >> 3; // cl
45507
45508      win.priority = b & 0x07; // p
45509
45510      b = packetData[++i];
45511      win.relativePositioning = (b & 0x80) >> 7; // rp
45512
45513      win.anchorVertical = b & 0x7f; // av
45514
45515      b = packetData[++i];
45516      win.anchorHorizontal = b; // ah
45517
45518      b = packetData[++i];
45519      win.anchorPoint = (b & 0xf0) >> 4; // ap
45520
45521      win.rowCount = b & 0x0f; // rc
45522
45523      b = packetData[++i];
45524      win.columnCount = b & 0x3f; // cc
45525
45526      b = packetData[++i];
45527      win.windowStyle = (b & 0x38) >> 3; // ws
45528
45529      win.penStyle = b & 0x07; // ps
45530      // The spec says there are (rowCount+1) "virtual rows"
45531
45532      win.virtualRowCount = win.rowCount + 1;
45533      return i;
45534    };
45535    /**
45536     * Parse and execute the SWA command.
45537     *
45538     * Set attributes of the current window.
45539     *
45540     * @param  {Integer} i        Current index in the 708 packet
45541     * @param  {Service} service  The service object to be affected
45542     * @return {Integer}          New index after parsing
45543     */
45544
45545
45546    Cea708Stream.prototype.setWindowAttributes = function (i, service) {
45547      var packetData = this.current708Packet.data;
45548      var b = packetData[i];
45549      var winAttr = service.currentWindow.winAttr;
45550      b = packetData[++i];
45551      winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
45552
vendor: 10,444 bytes, lines 45553-45901
45553      winAttr.fillRed = (b & 0x30) >> 4; // fr
45554
45555      winAttr.fillGreen = (b & 0x0c) >> 2; // fg
45556
45557      winAttr.fillBlue = b & 0x03; // fb
45558
45559      b = packetData[++i];
45560      winAttr.borderType = (b & 0xc0) >> 6; // bt
45561
45562      winAttr.borderRed = (b & 0x30) >> 4; // br
45563
45564      winAttr.borderGreen = (b & 0x0c) >> 2; // bg
45565
45566      winAttr.borderBlue = b & 0x03; // bb
45567
45568      b = packetData[++i];
45569      winAttr.borderType += (b & 0x80) >> 5; // bt
45570
45571      winAttr.wordWrap = (b & 0x40) >> 6; // ww
45572
45573      winAttr.printDirection = (b & 0x30) >> 4; // pd
45574
45575      winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
45576
45577      winAttr.justify = b & 0x03; // j
45578
45579      b = packetData[++i];
45580      winAttr.effectSpeed = (b & 0xf0) >> 4; // es
45581
45582      winAttr.effectDirection = (b & 0x0c) >> 2; // ed
45583
45584      winAttr.displayEffect = b & 0x03; // de
45585
45586      return i;
45587    };
45588    /**
45589     * Gather text from all displayed windows and push a caption to output.
45590     *
45591     * @param  {Integer} i        Current index in the 708 packet
45592     * @param  {Service} service  The service object to be affected
45593     */
45594
45595
45596    Cea708Stream.prototype.flushDisplayed = function (pts, service) {
45597      var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
45598      // display text in the correct order, but sample files so far have not shown any issue.
45599
45600      for (var winId = 0; winId < 8; winId++) {
45601        if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
45602          displayedText.push(service.windows[winId].getText());
45603        }
45604      }
45605
45606      service.endPts = pts;
45607      service.text = displayedText.join('\n\n');
45608      this.pushCaption(service);
45609      service.startPts = pts;
45610    };
45611    /**
45612     * Push a caption to output if the caption contains text.
45613     *
45614     * @param  {Service} service  The service object to be affected
45615     */
45616
45617
45618    Cea708Stream.prototype.pushCaption = function (service) {
45619      if (service.text !== '') {
45620        this.trigger('data', {
45621          startPts: service.startPts,
45622          endPts: service.endPts,
45623          text: service.text,
45624          stream: 'cc708_' + service.serviceNum
45625        });
45626        service.text = '';
45627        service.startPts = service.endPts;
45628      }
45629    };
45630    /**
45631     * Parse and execute the DSW command.
45632     *
45633     * Set visible property of windows based on the parsed bitmask.
45634     *
45635     * @param  {Integer} i        Current index in the 708 packet
45636     * @param  {Service} service  The service object to be affected
45637     * @return {Integer}          New index after parsing
45638     */
45639
45640
45641    Cea708Stream.prototype.displayWindows = function (i, service) {
45642      var packetData = this.current708Packet.data;
45643      var b = packetData[++i];
45644      var pts = this.getPts(i);
45645      this.flushDisplayed(pts, service);
45646
45647      for (var winId = 0; winId < 8; winId++) {
45648        if (b & 0x01 << winId) {
45649          service.windows[winId].visible = 1;
45650        }
45651      }
45652
45653      return i;
45654    };
45655    /**
45656     * Parse and execute the HDW command.
45657     *
45658     * Set visible property of windows based on the parsed bitmask.
45659     *
45660     * @param  {Integer} i        Current index in the 708 packet
45661     * @param  {Service} service  The service object to be affected
45662     * @return {Integer}          New index after parsing
45663     */
45664
45665
45666    Cea708Stream.prototype.hideWindows = function (i, service) {
45667      var packetData = this.current708Packet.data;
45668      var b = packetData[++i];
45669      var pts = this.getPts(i);
45670      this.flushDisplayed(pts, service);
45671
45672      for (var winId = 0; winId < 8; winId++) {
45673        if (b & 0x01 << winId) {
45674          service.windows[winId].visible = 0;
45675        }
45676      }
45677
45678      return i;
45679    };
45680    /**
45681     * Parse and execute the TGW command.
45682     *
45683     * Set visible property of windows based on the parsed bitmask.
45684     *
45685     * @param  {Integer} i        Current index in the 708 packet
45686     * @param  {Service} service  The service object to be affected
45687     * @return {Integer}          New index after parsing
45688     */
45689
45690
45691    Cea708Stream.prototype.toggleWindows = function (i, service) {
45692      var packetData = this.current708Packet.data;
45693      var b = packetData[++i];
45694      var pts = this.getPts(i);
45695      this.flushDisplayed(pts, service);
45696
45697      for (var winId = 0; winId < 8; winId++) {
45698        if (b & 0x01 << winId) {
45699          service.windows[winId].visible ^= 1;
45700        }
45701      }
45702
45703      return i;
45704    };
45705    /**
45706     * Parse and execute the CLW command.
45707     *
45708     * Clear text of windows based on the parsed bitmask.
45709     *
45710     * @param  {Integer} i        Current index in the 708 packet
45711     * @param  {Service} service  The service object to be affected
45712     * @return {Integer}          New index after parsing
45713     */
45714
45715
45716    Cea708Stream.prototype.clearWindows = function (i, service) {
45717      var packetData = this.current708Packet.data;
45718      var b = packetData[++i];
45719      var pts = this.getPts(i);
45720      this.flushDisplayed(pts, service);
45721
45722      for (var winId = 0; winId < 8; winId++) {
45723        if (b & 0x01 << winId) {
45724          service.windows[winId].clearText();
45725        }
45726      }
45727
45728      return i;
45729    };
45730    /**
45731     * Parse and execute the DLW command.
45732     *
45733     * Re-initialize windows based on the parsed bitmask.
45734     *
45735     * @param  {Integer} i        Current index in the 708 packet
45736     * @param  {Service} service  The service object to be affected
45737     * @return {Integer}          New index after parsing
45738     */
45739
45740
45741    Cea708Stream.prototype.deleteWindows = function (i, service) {
45742      var packetData = this.current708Packet.data;
45743      var b = packetData[++i];
45744      var pts = this.getPts(i);
45745      this.flushDisplayed(pts, service);
45746
45747      for (var winId = 0; winId < 8; winId++) {
45748        if (b & 0x01 << winId) {
45749          service.windows[winId].reset();
45750        }
45751      }
45752
45753      return i;
45754    };
45755    /**
45756     * Parse and execute the SPA command.
45757     *
45758     * Set pen attributes of the current window.
45759     *
45760     * @param  {Integer} i        Current index in the 708 packet
45761     * @param  {Service} service  The service object to be affected
45762     * @return {Integer}          New index after parsing
45763     */
45764
45765
45766    Cea708Stream.prototype.setPenAttributes = function (i, service) {
45767      var packetData = this.current708Packet.data;
45768      var b = packetData[i];
45769      var penAttr = service.currentWindow.penAttr;
45770      b = packetData[++i];
45771      penAttr.textTag = (b & 0xf0) >> 4; // tt
45772
45773      penAttr.offset = (b & 0x0c) >> 2; // o
45774
45775      penAttr.penSize = b & 0x03; // s
45776
45777      b = packetData[++i];
45778      penAttr.italics = (b & 0x80) >> 7; // i
45779
45780      penAttr.underline = (b & 0x40) >> 6; // u
45781
45782      penAttr.edgeType = (b & 0x38) >> 3; // et
45783
45784      penAttr.fontStyle = b & 0x07; // fs
45785
45786      return i;
45787    };
45788    /**
45789     * Parse and execute the SPC command.
45790     *
45791     * Set pen color of the current window.
45792     *
45793     * @param  {Integer} i        Current index in the 708 packet
45794     * @param  {Service} service  The service object to be affected
45795     * @return {Integer}          New index after parsing
45796     */
45797
45798
45799    Cea708Stream.prototype.setPenColor = function (i, service) {
45800      var packetData = this.current708Packet.data;
45801      var b = packetData[i];
45802      var penColor = service.currentWindow.penColor;
45803      b = packetData[++i];
45804      penColor.fgOpacity = (b & 0xc0) >> 6; // fo
45805
45806      penColor.fgRed = (b & 0x30) >> 4; // fr
45807
45808      penColor.fgGreen = (b & 0x0c) >> 2; // fg
45809
45810      penColor.fgBlue = b & 0x03; // fb
45811
45812      b = packetData[++i];
45813      penColor.bgOpacity = (b & 0xc0) >> 6; // bo
45814
45815      penColor.bgRed = (b & 0x30) >> 4; // br
45816
45817      penColor.bgGreen = (b & 0x0c) >> 2; // bg
45818
45819      penColor.bgBlue = b & 0x03; // bb
45820
45821      b = packetData[++i];
45822      penColor.edgeRed = (b & 0x30) >> 4; // er
45823
45824      penColor.edgeGreen = (b & 0x0c) >> 2; // eg
45825
45826      penColor.edgeBlue = b & 0x03; // eb
45827
45828      return i;
45829    };
45830    /**
45831     * Parse and execute the SPL command.
45832     *
45833     * Set pen location of the current window.
45834     *
45835     * @param  {Integer} i        Current index in the 708 packet
45836     * @param  {Service} service  The service object to be affected
45837     * @return {Integer}          New index after parsing
45838     */
45839
45840
45841    Cea708Stream.prototype.setPenLocation = function (i, service) {
45842      var packetData = this.current708Packet.data;
45843      var b = packetData[i];
45844      var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
45845
45846      service.currentWindow.pendingNewLine = true;
45847      b = packetData[++i];
45848      penLoc.row = b & 0x0f; // r
45849
45850      b = packetData[++i];
45851      penLoc.column = b & 0x3f; // c
45852
45853      return i;
45854    };
45855    /**
45856     * Execute the RST command.
45857     *
45858     * Reset service to a clean slate. Re-initialize.
45859     *
45860     * @param  {Integer} i        Current index in the 708 packet
45861     * @param  {Service} service  The service object to be affected
45862     * @return {Service}          Re-initialized service
45863     */
45864
45865
45866    Cea708Stream.prototype.reset = function (i, service) {
45867      var pts = this.getPts(i);
45868      this.flushDisplayed(pts, service);
45869      return this.initService(service.serviceNum, i);
45870    }; // This hash maps non-ASCII, special, and extended character codes to their
45871    // proper Unicode equivalent. The first keys that are only a single byte
45872    // are the non-standard ASCII characters, which simply map the CEA608 byte
45873    // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
45874    // character codes, but have their MSB bitmasked with 0x03 so that a lookup
45875    // can be performed regardless of the field and data channel on which the
45876    // character code was received.
45877
45878
45879    var CHARACTER_TRANSLATION = {
45880      0x2a: 0xe1,
45881      // á
45882      0x5c: 0xe9,
45883      // é
45884      0x5e: 0xed,
45885      // í
45886      0x5f: 0xf3,
45887      // ó
45888      0x60: 0xfa,
45889      // ú
45890      0x7b: 0xe7,
45891      // ç
45892      0x7c: 0xf7,
45893      // ÷
45894      0x7d: 0xd1,
45895      // Ñ
45896      0x7e: 0xf1,
45897      // ñ
45898      0x7f: 0x2588,
45899      // █
45900      0x0130: 0xae,
45901      // ®
45902      0x0131: 0xb0,
45903      // °
45904      0x0132: 0xbd,
45905      // ½
45906      0x0133: 0xbf,
45907      // ¿
45908      0x0134: 0x2122,
45909      // ™
45910      0x0135: 0xa2,
45911      // ¢
45912      0x0136: 0xa3,
45913      // £
45914      0x0137: 0x266a,
45915      // ♪
45916      0x0138: 0xe0,
45917      // à
45918      0x0139: 0xa0,
45919      //
45920      0x013a: 0xe8,
45921      // è
45922      0x013b: 0xe2,
45923      // â
45924      0x013c: 0xea,
45925      // ê
45926      0x013d: 0xee,
45927      // î
45928      0x013e: 0xf4,
45929      // ô
45930      0x013f: 0xfb,
45931      // û
45932      0x0220: 0xc1,
45933      // Á
45934      0x0221: 0xc9,
45935      // É
45936      0x0222: 0xd3,
45937      // Ó
45938      0x0223: 0xda,
45939      // Ú
45940      0x0224: 0xdc,
45941      // Ü
45942      0x0225: 0xfc,
45943      // ü
45944      0x0226: 0x2018,
45945      // ‘
45946      0x0227: 0xa1,
45947      // ¡
45948      0x0228: 0x2a,
45949      // *
45950      0x0229: 0x27,
45951      // '
45952      0x022a: 0x2014,
45953      // —
45954      0x022b: 0xa9,
45955      // ©
45956      0x022c: 0x2120,
45957      // ℠
45958      0x022d: 0x2022,
45959      // •
45960      0x022e: 0x201c,
45961      // “
45962      0x022f: 0x201d,
45963      // ”
45964      0x0230: 0xc0,
45965      // À
45966      0x0231: 0xc2,
45967      // Â
45968      0x0232: 0xc7,
45969      // Ç
45970      0x0233: 0xc8,
45971      // È
45972      0x0234: 0xca,
45973      // Ê
45974      0x0235: 0xcb,
45975      // Ë
45976      0x0236: 0xeb,
45977      // ë
45978      0x0237: 0xce,
45979      // Î
45980      0x0238: 0xcf,
45981      // Ï
45982      0x0239: 0xef,
45983      // ï
45984      0x023a: 0xd4,
45985      // Ô
45986      0x023b: 0xd9,
45987      // Ù
45988      0x023c: 0xf9,
45989      // ù
45990      0x023d: 0xdb,
45991      // Û
45992      0x023e: 0xab,
45993      // «
45994      0x023f: 0xbb,
45995      // »
45996      0x0320: 0xc3,
45997      // Ã
45998      0x0321: 0xe3,
45999      // ã
46000      0x0322: 0xcd,
46001      // Í
46002      0x0323: 0xcc,
46003      // Ì
46004      0x0324: 0xec,
46005      // ì
46006      0x0325: 0xd2,
46007      // Ò
46008      0x0326: 0xf2,
46009      // ò
46010      0x0327: 0xd5,
46011      // Õ
46012      0x0328: 0xf5,
46013      // õ
46014      0x0329: 0x7b,
46015      // {
46016      0x032a: 0x7d,
46017      // }
46018      0x032b: 0x5c,
46019      // \
46020      0x032c: 0x5e,
46021      // ^
46022      0x032d: 0x5f,
46023      // _
46024      0x032e: 0x7c,
46025      // |
46026      0x032f: 0x7e,
46027      // ~
46028      0x0330: 0xc4,
46029      // Ä
46030      0x0331: 0xe4,
vendor: 19,390 bytes, lines 46031-46532
46031      // ä
46032      0x0332: 0xd6,
46033      // Ö
46034      0x0333: 0xf6,
46035      // ö
46036      0x0334: 0xdf,
46037      // ß
46038      0x0335: 0xa5,
46039      // ¥
46040      0x0336: 0xa4,
46041      // ¤
46042      0x0337: 0x2502,
46043      // │
46044      0x0338: 0xc5,
46045      // Å
46046      0x0339: 0xe5,
46047      // å
46048      0x033a: 0xd8,
46049      // Ø
46050      0x033b: 0xf8,
46051      // ø
46052      0x033c: 0x250c,
46053      // ┌
46054      0x033d: 0x2510,
46055      // ┐
46056      0x033e: 0x2514,
46057      // └
46058      0x033f: 0x2518 // ┘
46059
46060    };
46061
46062    var getCharFromCode = function getCharFromCode(code) {
46063      if (code === null) {
46064        return '';
46065      }
46066
46067      code = CHARACTER_TRANSLATION[code] || code;
46068      return String.fromCharCode(code);
46069    }; // the index of the last row in a CEA-608 display buffer
46070
46071
46072    var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
46073    // getting it through bit logic.
46074
46075    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
46076    // cells. The "bottom" row is the last element in the outer array.
46077
46078    var createDisplayBuffer = function createDisplayBuffer() {
46079      var result = [],
46080          i = BOTTOM_ROW + 1;
46081
46082      while (i--) {
46083        result.push('');
46084      }
46085
46086      return result;
46087    };
46088
46089    var Cea608Stream = function Cea608Stream(field, dataChannel) {
46090      Cea608Stream.prototype.init.call(this);
46091      this.field_ = field || 0;
46092      this.dataChannel_ = dataChannel || 0;
46093      this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
46094      this.setConstants();
46095      this.reset();
46096
46097      this.push = function (packet) {
46098        var data, swap, char0, char1, text; // remove the parity bits
46099
46100        data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
46101
46102        if (data === this.lastControlCode_) {
46103          this.lastControlCode_ = null;
46104          return;
46105        } // Store control codes
46106
46107
46108        if ((data & 0xf000) === 0x1000) {
46109          this.lastControlCode_ = data;
46110        } else if (data !== this.PADDING_) {
46111          this.lastControlCode_ = null;
46112        }
46113
46114        char0 = data >>> 8;
46115        char1 = data & 0xff;
46116
46117        if (data === this.PADDING_) {
46118          return;
46119        } else if (data === this.RESUME_CAPTION_LOADING_) {
46120          this.mode_ = 'popOn';
46121        } else if (data === this.END_OF_CAPTION_) {
46122          // If an EOC is received while in paint-on mode, the displayed caption
46123          // text should be swapped to non-displayed memory as if it was a pop-on
46124          // caption. Because of that, we should explicitly switch back to pop-on
46125          // mode
46126          this.mode_ = 'popOn';
46127          this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
46128
46129          this.flushDisplayed(packet.pts); // flip memory
46130
46131          swap = this.displayed_;
46132          this.displayed_ = this.nonDisplayed_;
46133          this.nonDisplayed_ = swap; // start measuring the time to display the caption
46134
46135          this.startPts_ = packet.pts;
46136        } else if (data === this.ROLL_UP_2_ROWS_) {
46137          this.rollUpRows_ = 2;
46138          this.setRollUp(packet.pts);
46139        } else if (data === this.ROLL_UP_3_ROWS_) {
46140          this.rollUpRows_ = 3;
46141          this.setRollUp(packet.pts);
46142        } else if (data === this.ROLL_UP_4_ROWS_) {
46143          this.rollUpRows_ = 4;
46144          this.setRollUp(packet.pts);
46145        } else if (data === this.CARRIAGE_RETURN_) {
46146          this.clearFormatting(packet.pts);
46147          this.flushDisplayed(packet.pts);
46148          this.shiftRowsUp_();
46149          this.startPts_ = packet.pts;
46150        } else if (data === this.BACKSPACE_) {
46151          if (this.mode_ === 'popOn') {
46152            this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
46153          } else {
46154            this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
46155          }
46156        } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
46157          this.flushDisplayed(packet.pts);
46158          this.displayed_ = createDisplayBuffer();
46159        } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
46160          this.nonDisplayed_ = createDisplayBuffer();
46161        } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
46162          if (this.mode_ !== 'paintOn') {
46163            // NOTE: This should be removed when proper caption positioning is
46164            // implemented
46165            this.flushDisplayed(packet.pts);
46166            this.displayed_ = createDisplayBuffer();
46167          }
46168
46169          this.mode_ = 'paintOn';
46170          this.startPts_ = packet.pts; // Append special characters to caption text
46171        } else if (this.isSpecialCharacter(char0, char1)) {
46172          // Bitmask char0 so that we can apply character transformations
46173          // regardless of field and data channel.
46174          // Then byte-shift to the left and OR with char1 so we can pass the
46175          // entire character code to `getCharFromCode`.
46176          char0 = (char0 & 0x03) << 8;
46177          text = getCharFromCode(char0 | char1);
46178          this[this.mode_](packet.pts, text);
46179          this.column_++; // Append extended characters to caption text
46180        } else if (this.isExtCharacter(char0, char1)) {
46181          // Extended characters always follow their "non-extended" equivalents.
46182          // IE if a "è" is desired, you'll always receive "eè"; non-compliant
46183          // decoders are supposed to drop the "è", while compliant decoders
46184          // backspace the "e" and insert "è".
46185          // Delete the previous character
46186          if (this.mode_ === 'popOn') {
46187            this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
46188          } else {
46189            this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
46190          } // Bitmask char0 so that we can apply character transformations
46191          // regardless of field and data channel.
46192          // Then byte-shift to the left and OR with char1 so we can pass the
46193          // entire character code to `getCharFromCode`.
46194
46195
46196          char0 = (char0 & 0x03) << 8;
46197          text = getCharFromCode(char0 | char1);
46198          this[this.mode_](packet.pts, text);
46199          this.column_++; // Process mid-row codes
46200        } else if (this.isMidRowCode(char0, char1)) {
46201          // Attributes are not additive, so clear all formatting
46202          this.clearFormatting(packet.pts); // According to the standard, mid-row codes
46203          // should be replaced with spaces, so add one now
46204
46205          this[this.mode_](packet.pts, ' ');
46206          this.column_++;
46207
46208          if ((char1 & 0xe) === 0xe) {
46209            this.addFormatting(packet.pts, ['i']);
46210          }
46211
46212          if ((char1 & 0x1) === 0x1) {
46213            this.addFormatting(packet.pts, ['u']);
46214          } // Detect offset control codes and adjust cursor
46215
46216        } else if (this.isOffsetControlCode(char0, char1)) {
46217          // Cursor position is set by indent PAC (see below) in 4-column
46218          // increments, with an additional offset code of 1-3 to reach any
46219          // of the 32 columns specified by CEA-608. So all we need to do
46220          // here is increment the column cursor by the given offset.
46221          this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
46222        } else if (this.isPAC(char0, char1)) {
46223          // There's no logic for PAC -> row mapping, so we have to just
46224          // find the row code in an array and use its index :(
46225          var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
46226
46227          if (this.mode_ === 'rollUp') {
46228            // This implies that the base row is incorrectly set.
46229            // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
46230            // of roll-up rows set.
46231            if (row - this.rollUpRows_ + 1 < 0) {
46232              row = this.rollUpRows_ - 1;
46233            }
46234
46235            this.setRollUp(packet.pts, row);
46236          }
46237
46238          if (row !== this.row_) {
46239            // formatting is only persistent for current row
46240            this.clearFormatting(packet.pts);
46241            this.row_ = row;
46242          } // All PACs can apply underline, so detect and apply
46243          // (All odd-numbered second bytes set underline)
46244
46245
46246          if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
46247            this.addFormatting(packet.pts, ['u']);
46248          }
46249
46250          if ((data & 0x10) === 0x10) {
46251            // We've got an indent level code. Each successive even number
46252            // increments the column cursor by 4, so we can get the desired
46253            // column position by bit-shifting to the right (to get n/2)
46254            // and multiplying by 4.
46255            this.column_ = ((data & 0xe) >> 1) * 4;
46256          }
46257
46258          if (this.isColorPAC(char1)) {
46259            // it's a color code, though we only support white, which
46260            // can be either normal or italicized. white italics can be
46261            // either 0x4e or 0x6e depending on the row, so we just
46262            // bitwise-and with 0xe to see if italics should be turned on
46263            if ((char1 & 0xe) === 0xe) {
46264              this.addFormatting(packet.pts, ['i']);
46265            }
46266          } // We have a normal character in char0, and possibly one in char1
46267
46268        } else if (this.isNormalChar(char0)) {
46269          if (char1 === 0x00) {
46270            char1 = null;
46271          }
46272
46273          text = getCharFromCode(char0);
46274          text += getCharFromCode(char1);
46275          this[this.mode_](packet.pts, text);
46276          this.column_ += text.length;
46277        } // finish data processing
46278
46279      };
46280    };
46281
46282    Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
46283    // display buffer
46284
46285    Cea608Stream.prototype.flushDisplayed = function (pts) {
46286      var content = this.displayed_ // remove spaces from the start and end of the string
46287      .map(function (row, index) {
46288        try {
46289          return row.trim();
46290        } catch (e) {
46291          // Ordinarily, this shouldn't happen. However, caption
46292          // parsing errors should not throw exceptions and
46293          // break playback.
46294          this.trigger('log', {
46295            level: 'warn',
46296            message: 'Skipping a malformed 608 caption at index ' + index + '.'
46297          });
46298          return '';
46299        }
46300      }, this) // combine all text rows to display in one cue
46301      .join('\n') // and remove blank rows from the start and end, but not the middle
46302      .replace(/^\n+|\n+$/g, '');
46303
46304      if (content.length) {
46305        this.trigger('data', {
46306          startPts: this.startPts_,
46307          endPts: pts,
46308          text: content,
46309          stream: this.name_
46310        });
46311      }
46312    };
46313    /**
46314     * Zero out the data, used for startup and on seek
46315     */
46316
46317
46318    Cea608Stream.prototype.reset = function () {
46319      this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
46320      // actually display captions. If a caption is shifted to a row
46321      // with a lower index than this, it is cleared from the display
46322      // buffer
46323
46324      this.topRow_ = 0;
46325      this.startPts_ = 0;
46326      this.displayed_ = createDisplayBuffer();
46327      this.nonDisplayed_ = createDisplayBuffer();
46328      this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
46329
46330      this.column_ = 0;
46331      this.row_ = BOTTOM_ROW;
46332      this.rollUpRows_ = 2; // This variable holds currently-applied formatting
46333
46334      this.formatting_ = [];
46335    };
46336    /**
46337     * Sets up control code and related constants for this instance
46338     */
46339
46340
46341    Cea608Stream.prototype.setConstants = function () {
46342      // The following attributes have these uses:
46343      // ext_ :    char0 for mid-row codes, and the base for extended
46344      //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
46345      //           extended codes)
46346      // control_: char0 for control codes, except byte-shifted to the
46347      //           left so that we can do this.control_ | CONTROL_CODE
46348      // offset_:  char0 for tab offset codes
46349      //
46350      // It's also worth noting that control codes, and _only_ control codes,
46351      // differ between field 1 and field2. Field 2 control codes are always
46352      // their field 1 value plus 1. That's why there's the "| field" on the
46353      // control value.
46354      if (this.dataChannel_ === 0) {
46355        this.BASE_ = 0x10;
46356        this.EXT_ = 0x11;
46357        this.CONTROL_ = (0x14 | this.field_) << 8;
46358        this.OFFSET_ = 0x17;
46359      } else if (this.dataChannel_ === 1) {
46360        this.BASE_ = 0x18;
46361        this.EXT_ = 0x19;
46362        this.CONTROL_ = (0x1c | this.field_) << 8;
46363        this.OFFSET_ = 0x1f;
46364      } // Constants for the LSByte command codes recognized by Cea608Stream. This
46365      // list is not exhaustive. For a more comprehensive listing and semantics see
46366      // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
46367      // Padding
46368
46369
46370      this.PADDING_ = 0x0000; // Pop-on Mode
46371
46372      this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
46373      this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
46374
46375      this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
46376      this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
46377      this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
46378      this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
46379
46380      this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
46381
46382      this.BACKSPACE_ = this.CONTROL_ | 0x21;
46383      this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
46384      this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
46385    };
46386    /**
46387     * Detects if the 2-byte packet data is a special character
46388     *
46389     * Special characters have a second byte in the range 0x30 to 0x3f,
46390     * with the first byte being 0x11 (for data channel 1) or 0x19 (for
46391     * data channel 2).
46392     *
46393     * @param  {Integer} char0 The first byte
46394     * @param  {Integer} char1 The second byte
46395     * @return {Boolean}       Whether the 2 bytes are an special character
46396     */
46397
46398
46399    Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
46400      return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
46401    };
46402    /**
46403     * Detects if the 2-byte packet data is an extended character
46404     *
46405     * Extended characters have a second byte in the range 0x20 to 0x3f,
46406     * with the first byte being 0x12 or 0x13 (for data channel 1) or
46407     * 0x1a or 0x1b (for data channel 2).
46408     *
46409     * @param  {Integer} char0 The first byte
46410     * @param  {Integer} char1 The second byte
46411     * @return {Boolean}       Whether the 2 bytes are an extended character
46412     */
46413
46414
46415    Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
46416      return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
46417    };
46418    /**
46419     * Detects if the 2-byte packet is a mid-row code
46420     *
46421     * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
46422     * the first byte being 0x11 (for data channel 1) or 0x19 (for data
46423     * channel 2).
46424     *
46425     * @param  {Integer} char0 The first byte
46426     * @param  {Integer} char1 The second byte
46427     * @return {Boolean}       Whether the 2 bytes are a mid-row code
46428     */
46429
46430
46431    Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
46432      return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
46433    };
46434    /**
46435     * Detects if the 2-byte packet is an offset control code
46436     *
46437     * Offset control codes have a second byte in the range 0x21 to 0x23,
46438     * with the first byte being 0x17 (for data channel 1) or 0x1f (for
46439     * data channel 2).
46440     *
46441     * @param  {Integer} char0 The first byte
46442     * @param  {Integer} char1 The second byte
46443     * @return {Boolean}       Whether the 2 bytes are an offset control code
46444     */
46445
46446
46447    Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
46448      return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
46449    };
46450    /**
46451     * Detects if the 2-byte packet is a Preamble Address Code
46452     *
46453     * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
46454     * or 0x18 to 0x1f (for data channel 2), with the second byte in the
46455     * range 0x40 to 0x7f.
46456     *
46457     * @param  {Integer} char0 The first byte
46458     * @param  {Integer} char1 The second byte
46459     * @return {Boolean}       Whether the 2 bytes are a PAC
46460     */
46461
46462
46463    Cea608Stream.prototype.isPAC = function (char0, char1) {
46464      return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
46465    };
46466    /**
46467     * Detects if a packet's second byte is in the range of a PAC color code
46468     *
46469     * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
46470     * 0x60 to 0x6f.
46471     *
46472     * @param  {Integer} char1 The second byte
46473     * @return {Boolean}       Whether the byte is a color PAC
46474     */
46475
46476
46477    Cea608Stream.prototype.isColorPAC = function (char1) {
46478      return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
46479    };
46480    /**
46481     * Detects if a single byte is in the range of a normal character
46482     *
46483     * Normal text bytes are in the range 0x20 to 0x7f.
46484     *
46485     * @param  {Integer} char  The byte
46486     * @return {Boolean}       Whether the byte is a normal character
46487     */
46488
46489
46490    Cea608Stream.prototype.isNormalChar = function (_char2) {
46491      return _char2 >= 0x20 && _char2 <= 0x7f;
46492    };
46493    /**
46494     * Configures roll-up
46495     *
46496     * @param  {Integer} pts         Current PTS
46497     * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
46498     *                               a new position
46499     */
46500
46501
46502    Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
46503      // Reset the base row to the bottom row when switching modes
46504      if (this.mode_ !== 'rollUp') {
46505        this.row_ = BOTTOM_ROW;
46506        this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
46507
46508        this.flushDisplayed(pts);
46509        this.nonDisplayed_ = createDisplayBuffer();
46510        this.displayed_ = createDisplayBuffer();
46511      }
46512
46513      if (newBaseRow !== undefined && newBaseRow !== this.row_) {
46514        // move currently displayed captions (up or down) to the new base row
46515        for (var i = 0; i < this.rollUpRows_; i++) {
46516          this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
46517          this.displayed_[this.row_ - i] = '';
46518        }
46519      }
46520
46521      if (newBaseRow === undefined) {
46522        newBaseRow = this.row_;
46523      }
46524
46525      this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
46526    }; // Adds the opening HTML tag for the passed character to the caption text,
46527    // and keeps track of it for later closing
46528
46529
46530    Cea608Stream.prototype.addFormatting = function (pts, format) {
46531      this.formatting_ = this.formatting_.concat(format);
46532      var text = format.reduce(function (text, format) {
vendor: 15,531 bytes, lines 46533-46977
46533        return text + '<' + format + '>';
46534      }, '');
46535      this[this.mode_](pts, text);
46536    }; // Adds HTML closing tags for current formatting to caption text and
46537    // clears remembered formatting
46538
46539
46540    Cea608Stream.prototype.clearFormatting = function (pts) {
46541      if (!this.formatting_.length) {
46542        return;
46543      }
46544
46545      var text = this.formatting_.reverse().reduce(function (text, format) {
46546        return text + '</' + format + '>';
46547      }, '');
46548      this.formatting_ = [];
46549      this[this.mode_](pts, text);
46550    }; // Mode Implementations
46551
46552
46553    Cea608Stream.prototype.popOn = function (pts, text) {
46554      var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
46555
46556      baseRow += text;
46557      this.nonDisplayed_[this.row_] = baseRow;
46558    };
46559
46560    Cea608Stream.prototype.rollUp = function (pts, text) {
46561      var baseRow = this.displayed_[this.row_];
46562      baseRow += text;
46563      this.displayed_[this.row_] = baseRow;
46564    };
46565
46566    Cea608Stream.prototype.shiftRowsUp_ = function () {
46567      var i; // clear out inactive rows
46568
46569      for (i = 0; i < this.topRow_; i++) {
46570        this.displayed_[i] = '';
46571      }
46572
46573      for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
46574        this.displayed_[i] = '';
46575      } // shift displayed rows up
46576
46577
46578      for (i = this.topRow_; i < this.row_; i++) {
46579        this.displayed_[i] = this.displayed_[i + 1];
46580      } // clear out the bottom row
46581
46582
46583      this.displayed_[this.row_] = '';
46584    };
46585
46586    Cea608Stream.prototype.paintOn = function (pts, text) {
46587      var baseRow = this.displayed_[this.row_];
46588      baseRow += text;
46589      this.displayed_[this.row_] = baseRow;
46590    }; // exports
46591
46592
46593    var captionStream = {
46594      CaptionStream: CaptionStream$1,
46595      Cea608Stream: Cea608Stream,
46596      Cea708Stream: Cea708Stream
46597    };
46598    /**
46599     * mux.js
46600     *
46601     * Copyright (c) Brightcove
46602     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46603     */
46604
46605    var streamTypes = {
46606      H264_STREAM_TYPE: 0x1B,
46607      ADTS_STREAM_TYPE: 0x0F,
46608      METADATA_STREAM_TYPE: 0x15
46609    };
46610    var MAX_TS = 8589934592;
46611    var RO_THRESH = 4294967296;
46612    var TYPE_SHARED = 'shared';
46613
46614    var handleRollover$1 = function handleRollover(value, reference) {
46615      var direction = 1;
46616
46617      if (value > reference) {
46618        // If the current timestamp value is greater than our reference timestamp and we detect a
46619        // timestamp rollover, this means the roll over is happening in the opposite direction.
46620        // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
46621        // point will be set to a small number, e.g. 1. The user then seeks backwards over the
46622        // rollover point. In loading this segment, the timestamp values will be very large,
46623        // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
46624        // the time stamp to be `value - 2^33`.
46625        direction = -1;
46626      } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
46627      // cause an incorrect adjustment.
46628
46629
46630      while (Math.abs(reference - value) > RO_THRESH) {
46631        value += direction * MAX_TS;
46632      }
46633
46634      return value;
46635    };
46636
46637    var TimestampRolloverStream$1 = function TimestampRolloverStream(type) {
46638      var lastDTS, referenceDTS;
46639      TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
46640      // video and audio. We could use `undefined` here, but having a string
46641      // makes debugging a little clearer.
46642
46643      this.type_ = type || TYPE_SHARED;
46644
46645      this.push = function (data) {
46646        // Any "shared" rollover streams will accept _all_ data. Otherwise,
46647        // streams will only accept data that matches their type.
46648        if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
46649          return;
46650        }
46651
46652        if (referenceDTS === undefined) {
46653          referenceDTS = data.dts;
46654        }
46655
46656        data.dts = handleRollover$1(data.dts, referenceDTS);
46657        data.pts = handleRollover$1(data.pts, referenceDTS);
46658        lastDTS = data.dts;
46659        this.trigger('data', data);
46660      };
46661
46662      this.flush = function () {
46663        referenceDTS = lastDTS;
46664        this.trigger('done');
46665      };
46666
46667      this.endTimeline = function () {
46668        this.flush();
46669        this.trigger('endedtimeline');
46670      };
46671
46672      this.discontinuity = function () {
46673        referenceDTS = void 0;
46674        lastDTS = void 0;
46675      };
46676
46677      this.reset = function () {
46678        this.discontinuity();
46679        this.trigger('reset');
46680      };
46681    };
46682
46683    TimestampRolloverStream$1.prototype = new stream();
46684    var timestampRolloverStream = {
46685      TimestampRolloverStream: TimestampRolloverStream$1,
46686      handleRollover: handleRollover$1
46687    };
46688
46689    var percentEncode$1 = function percentEncode(bytes, start, end) {
46690      var i,
46691          result = '';
46692
46693      for (i = start; i < end; i++) {
46694        result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
46695      }
46696
46697      return result;
46698    },
46699        // return the string representation of the specified byte range,
46700    // interpreted as UTf-8.
46701    parseUtf8 = function parseUtf8(bytes, start, end) {
46702      return decodeURIComponent(percentEncode$1(bytes, start, end));
46703    },
46704        // return the string representation of the specified byte range,
46705    // interpreted as ISO-8859-1.
46706    parseIso88591$1 = function parseIso88591(bytes, start, end) {
46707      return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
46708    },
46709        parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
46710      return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
46711    },
46712        tagParsers = {
46713      TXXX: function TXXX(tag) {
46714        var i;
46715
46716        if (tag.data[0] !== 3) {
46717          // ignore frames with unrecognized character encodings
46718          return;
46719        }
46720
46721        for (i = 1; i < tag.data.length; i++) {
46722          if (tag.data[i] === 0) {
46723            // parse the text fields
46724            tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
46725
46726            tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
46727            break;
46728          }
46729        }
46730
46731        tag.data = tag.value;
46732      },
46733      WXXX: function WXXX(tag) {
46734        var i;
46735
46736        if (tag.data[0] !== 3) {
46737          // ignore frames with unrecognized character encodings
46738          return;
46739        }
46740
46741        for (i = 1; i < tag.data.length; i++) {
46742          if (tag.data[i] === 0) {
46743            // parse the description and URL fields
46744            tag.description = parseUtf8(tag.data, 1, i);
46745            tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
46746            break;
46747          }
46748        }
46749      },
46750      PRIV: function PRIV(tag) {
46751        var i;
46752
46753        for (i = 0; i < tag.data.length; i++) {
46754          if (tag.data[i] === 0) {
46755            // parse the description and URL fields
46756            tag.owner = parseIso88591$1(tag.data, 0, i);
46757            break;
46758          }
46759        }
46760
46761        tag.privateData = tag.data.subarray(i + 1);
46762        tag.data = tag.privateData;
46763      }
46764    },
46765        _MetadataStream;
46766
46767    _MetadataStream = function MetadataStream(options) {
46768      var settings = {
46769        // the bytes of the program-level descriptor field in MP2T
46770        // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
46771        // program element descriptors"
46772        descriptor: options && options.descriptor
46773      },
46774          // the total size in bytes of the ID3 tag being parsed
46775      tagSize = 0,
46776          // tag data that is not complete enough to be parsed
46777      buffer = [],
46778          // the total number of bytes currently in the buffer
46779      bufferSize = 0,
46780          i;
46781
46782      _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
46783      // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
46784
46785
46786      this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
46787
46788      if (settings.descriptor) {
46789        for (i = 0; i < settings.descriptor.length; i++) {
46790          this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
46791        }
46792      }
46793
46794      this.push = function (chunk) {
46795        var tag, frameStart, frameSize, frame, i, frameHeader;
46796
46797        if (chunk.type !== 'timed-metadata') {
46798          return;
46799        } // if data_alignment_indicator is set in the PES header,
46800        // we must have the start of a new ID3 tag. Assume anything
46801        // remaining in the buffer was malformed and throw it out
46802
46803
46804        if (chunk.dataAlignmentIndicator) {
46805          bufferSize = 0;
46806          buffer.length = 0;
46807        } // ignore events that don't look like ID3 data
46808
46809
46810        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))) {
46811          this.trigger('log', {
46812            level: 'warn',
46813            message: 'Skipping unrecognized metadata packet'
46814          });
46815          return;
46816        } // add this chunk to the data we've collected so far
46817
46818
46819        buffer.push(chunk);
46820        bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
46821
46822        if (buffer.length === 1) {
46823          // the frame size is transmitted as a 28-bit integer in the
46824          // last four bytes of the ID3 header.
46825          // The most significant bit of each byte is dropped and the
46826          // results concatenated to recover the actual value.
46827          tagSize = parseSyncSafeInteger$1(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
46828          // convenient for our comparisons to include it
46829
46830          tagSize += 10;
46831        } // if the entire frame has not arrived, wait for more data
46832
46833
46834        if (bufferSize < tagSize) {
46835          return;
46836        } // collect the entire frame so it can be parsed
46837
46838
46839        tag = {
46840          data: new Uint8Array(tagSize),
46841          frames: [],
46842          pts: buffer[0].pts,
46843          dts: buffer[0].dts
46844        };
46845
46846        for (i = 0; i < tagSize;) {
46847          tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
46848          i += buffer[0].data.byteLength;
46849          bufferSize -= buffer[0].data.byteLength;
46850          buffer.shift();
46851        } // find the start of the first frame and the end of the tag
46852
46853
46854        frameStart = 10;
46855
46856        if (tag.data[5] & 0x40) {
46857          // advance the frame start past the extended header
46858          frameStart += 4; // header size field
46859
46860          frameStart += parseSyncSafeInteger$1(tag.data.subarray(10, 14)); // clip any padding off the end
46861
46862          tagSize -= parseSyncSafeInteger$1(tag.data.subarray(16, 20));
46863        } // parse one or more ID3 frames
46864        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46865
46866
46867        do {
46868          // determine the number of bytes in this frame
46869          frameSize = parseSyncSafeInteger$1(tag.data.subarray(frameStart + 4, frameStart + 8));
46870
46871          if (frameSize < 1) {
46872            this.trigger('log', {
46873              level: 'warn',
46874              message: 'Malformed ID3 frame encountered. Skipping metadata parsing.'
46875            });
46876            return;
46877          }
46878
46879          frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
46880          frame = {
46881            id: frameHeader,
46882            data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
46883          };
46884          frame.key = frame.id;
46885
46886          if (tagParsers[frame.id]) {
46887            tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
46888            // time for raw AAC data
46889
46890            if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
46891              var d = frame.data,
46892                  size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
46893              size *= 4;
46894              size += d[7] & 0x03;
46895              frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
46896              // on the value of this frame
46897              // we couldn't have known the appropriate pts and dts before
46898              // parsing this ID3 tag so set those values now
46899
46900              if (tag.pts === undefined && tag.dts === undefined) {
46901                tag.pts = frame.timeStamp;
46902                tag.dts = frame.timeStamp;
46903              }
46904
46905              this.trigger('timestamp', frame);
46906            }
46907          }
46908
46909          tag.frames.push(frame);
46910          frameStart += 10; // advance past the frame header
46911
46912          frameStart += frameSize; // advance past the frame body
46913        } while (frameStart < tagSize);
46914
46915        this.trigger('data', tag);
46916      };
46917    };
46918
46919    _MetadataStream.prototype = new stream();
46920    var metadataStream = _MetadataStream;
46921    var TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
46922
46923    var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
46924
46925
46926    var MP2T_PACKET_LENGTH$1 = 188,
46927        // bytes
46928    SYNC_BYTE$1 = 0x47;
46929    /**
46930     * Splits an incoming stream of binary data into MPEG-2 Transport
46931     * Stream packets.
46932     */
46933
46934    _TransportPacketStream = function TransportPacketStream() {
46935      var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
46936          bytesInBuffer = 0;
46937
46938      _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
46939
46940      /**
46941       * Split a stream of data into M2TS packets
46942      **/
46943
46944
46945      this.push = function (bytes) {
46946        var startIndex = 0,
46947            endIndex = MP2T_PACKET_LENGTH$1,
46948            everything; // If there are bytes remaining from the last segment, prepend them to the
46949        // bytes that were pushed in
46950
46951        if (bytesInBuffer) {
46952          everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
46953          everything.set(buffer.subarray(0, bytesInBuffer));
46954          everything.set(bytes, bytesInBuffer);
46955          bytesInBuffer = 0;
46956        } else {
46957          everything = bytes;
46958        } // While we have enough data for a packet
46959
46960
46961        while (endIndex < everything.byteLength) {
46962          // Look for a pair of start and end sync bytes in the data..
46963          if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
46964            // We found a packet so emit it and jump one whole packet forward in
46965            // the stream
46966            this.trigger('data', everything.subarray(startIndex, endIndex));
46967            startIndex += MP2T_PACKET_LENGTH$1;
46968            endIndex += MP2T_PACKET_LENGTH$1;
46969            continue;
46970          } // If we get here, we have somehow become de-synchronized and we need to step
46971          // forward one byte at a time until we find a pair of sync bytes that denote
46972          // a packet
46973
46974
46975          startIndex++;
46976          endIndex++;
46977        } // If there was some data left over at the end of the segment that couldn't
vendor: 17,820 bytes, lines 46978-47459
46978        // possibly be a whole packet, keep it because it might be the start of a packet
46979        // that continues in the next segment
46980
46981
46982        if (startIndex < everything.byteLength) {
46983          buffer.set(everything.subarray(startIndex), 0);
46984          bytesInBuffer = everything.byteLength - startIndex;
46985        }
46986      };
46987      /**
46988       * Passes identified M2TS packets to the TransportParseStream to be parsed
46989      **/
46990
46991
46992      this.flush = function () {
46993        // If the buffer contains a whole packet when we are being flushed, emit it
46994        // and empty the buffer. Otherwise hold onto the data because it may be
46995        // important for decoding the next segment
46996        if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
46997          this.trigger('data', buffer);
46998          bytesInBuffer = 0;
46999        }
47000
47001        this.trigger('done');
47002      };
47003
47004      this.endTimeline = function () {
47005        this.flush();
47006        this.trigger('endedtimeline');
47007      };
47008
47009      this.reset = function () {
47010        bytesInBuffer = 0;
47011        this.trigger('reset');
47012      };
47013    };
47014
47015    _TransportPacketStream.prototype = new stream();
47016    /**
47017     * Accepts an MP2T TransportPacketStream and emits data events with parsed
47018     * forms of the individual transport stream packets.
47019     */
47020
47021    _TransportParseStream = function TransportParseStream() {
47022      var parsePsi, parsePat, parsePmt, self;
47023
47024      _TransportParseStream.prototype.init.call(this);
47025
47026      self = this;
47027      this.packetsWaitingForPmt = [];
47028      this.programMapTable = undefined;
47029
47030      parsePsi = function parsePsi(payload, psi) {
47031        var offset = 0; // PSI packets may be split into multiple sections and those
47032        // sections may be split into multiple packets. If a PSI
47033        // section starts in this packet, the payload_unit_start_indicator
47034        // will be true and the first byte of the payload will indicate
47035        // the offset from the current position to the start of the
47036        // section.
47037
47038        if (psi.payloadUnitStartIndicator) {
47039          offset += payload[offset] + 1;
47040        }
47041
47042        if (psi.type === 'pat') {
47043          parsePat(payload.subarray(offset), psi);
47044        } else {
47045          parsePmt(payload.subarray(offset), psi);
47046        }
47047      };
47048
47049      parsePat = function parsePat(payload, pat) {
47050        pat.section_number = payload[7]; // eslint-disable-line camelcase
47051
47052        pat.last_section_number = payload[8]; // eslint-disable-line camelcase
47053        // skip the PSI header and parse the first PMT entry
47054
47055        self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
47056        pat.pmtPid = self.pmtPid;
47057      };
47058      /**
47059       * Parse out the relevant fields of a Program Map Table (PMT).
47060       * @param payload {Uint8Array} the PMT-specific portion of an MP2T
47061       * packet. The first byte in this array should be the table_id
47062       * field.
47063       * @param pmt {object} the object that should be decorated with
47064       * fields parsed from the PMT.
47065       */
47066
47067
47068      parsePmt = function parsePmt(payload, pmt) {
47069        var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
47070        // take effect. We don't believe this should ever be the case
47071        // for HLS but we'll ignore "forward" PMT declarations if we see
47072        // them. Future PMT declarations have the current_next_indicator
47073        // set to zero.
47074
47075        if (!(payload[5] & 0x01)) {
47076          return;
47077        } // overwrite any existing program map table
47078
47079
47080        self.programMapTable = {
47081          video: null,
47082          audio: null,
47083          'timed-metadata': {}
47084        }; // the mapping table ends at the end of the current section
47085
47086        sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
47087        tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
47088        // long the program info descriptors are
47089
47090        programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
47091
47092        offset = 12 + programInfoLength;
47093
47094        while (offset < tableEnd) {
47095          var streamType = payload[offset];
47096          var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
47097          // TODO: should this be done for metadata too? for now maintain behavior of
47098          //       multiple metadata streams
47099
47100          if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
47101            self.programMapTable.video = pid;
47102          } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
47103            self.programMapTable.audio = pid;
47104          } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
47105            // map pid to stream type for metadata streams
47106            self.programMapTable['timed-metadata'][pid] = streamType;
47107          } // move to the next table entry
47108          // skip past the elementary stream descriptors, if present
47109
47110
47111          offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
47112        } // record the map on the packet as well
47113
47114
47115        pmt.programMapTable = self.programMapTable;
47116      };
47117      /**
47118       * Deliver a new MP2T packet to the next stream in the pipeline.
47119       */
47120
47121
47122      this.push = function (packet) {
47123        var result = {},
47124            offset = 4;
47125        result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
47126
47127        result.pid = packet[1] & 0x1f;
47128        result.pid <<= 8;
47129        result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
47130        // fifth byte of the TS packet header. The adaptation field is
47131        // used to add stuffing to PES packets that don't fill a complete
47132        // TS packet, and to specify some forms of timing and control data
47133        // that we do not currently use.
47134
47135        if ((packet[3] & 0x30) >>> 4 > 0x01) {
47136          offset += packet[offset] + 1;
47137        } // parse the rest of the packet based on the type
47138
47139
47140        if (result.pid === 0) {
47141          result.type = 'pat';
47142          parsePsi(packet.subarray(offset), result);
47143          this.trigger('data', result);
47144        } else if (result.pid === this.pmtPid) {
47145          result.type = 'pmt';
47146          parsePsi(packet.subarray(offset), result);
47147          this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
47148
47149          while (this.packetsWaitingForPmt.length) {
47150            this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
47151          }
47152        } else if (this.programMapTable === undefined) {
47153          // When we have not seen a PMT yet, defer further processing of
47154          // PES packets until one has been parsed
47155          this.packetsWaitingForPmt.push([packet, offset, result]);
47156        } else {
47157          this.processPes_(packet, offset, result);
47158        }
47159      };
47160
47161      this.processPes_ = function (packet, offset, result) {
47162        // set the appropriate stream type
47163        if (result.pid === this.programMapTable.video) {
47164          result.streamType = streamTypes.H264_STREAM_TYPE;
47165        } else if (result.pid === this.programMapTable.audio) {
47166          result.streamType = streamTypes.ADTS_STREAM_TYPE;
47167        } else {
47168          // if not video or audio, it is timed-metadata or unknown
47169          // if unknown, streamType will be undefined
47170          result.streamType = this.programMapTable['timed-metadata'][result.pid];
47171        }
47172
47173        result.type = 'pes';
47174        result.data = packet.subarray(offset);
47175        this.trigger('data', result);
47176      };
47177    };
47178
47179    _TransportParseStream.prototype = new stream();
47180    _TransportParseStream.STREAM_TYPES = {
47181      h264: 0x1b,
47182      adts: 0x0f
47183    };
47184    /**
47185     * Reconsistutes program elementary stream (PES) packets from parsed
47186     * transport stream packets. That is, if you pipe an
47187     * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
47188     * events will be events which capture the bytes for individual PES
47189     * packets plus relevant metadata that has been extracted from the
47190     * container.
47191     */
47192
47193    _ElementaryStream = function ElementaryStream() {
47194      var self = this,
47195          segmentHadPmt = false,
47196          // PES packet fragments
47197      video = {
47198        data: [],
47199        size: 0
47200      },
47201          audio = {
47202        data: [],
47203        size: 0
47204      },
47205          timedMetadata = {
47206        data: [],
47207        size: 0
47208      },
47209          programMapTable,
47210          parsePes = function parsePes(payload, pes) {
47211        var ptsDtsFlags;
47212        var startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
47213
47214        pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
47215        // that are frame data that is continuing from the previous fragment. This
47216        // is to check that the pes data is the start of a new pes payload
47217
47218        if (startPrefix !== 1) {
47219          return;
47220        } // get the packet length, this will be 0 for video
47221
47222
47223        pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
47224
47225        pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
47226        // and a DTS value. Determine what combination of values is
47227        // available to work with.
47228
47229        ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
47230        // performs all bitwise operations on 32-bit integers but javascript
47231        // supports a much greater range (52-bits) of integer using standard
47232        // mathematical operations.
47233        // We construct a 31-bit value using bitwise operators over the 31
47234        // most significant bits and then multiply by 4 (equal to a left-shift
47235        // of 2) before we add the final 2 least significant bits of the
47236        // timestamp (equal to an OR.)
47237
47238        if (ptsDtsFlags & 0xC0) {
47239          // the PTS and DTS are not written out directly. For information
47240          // on how they are encoded, see
47241          // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
47242          pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
47243          pes.pts *= 4; // Left shift by 2
47244
47245          pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
47246
47247          pes.dts = pes.pts;
47248
47249          if (ptsDtsFlags & 0x40) {
47250            pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
47251            pes.dts *= 4; // Left shift by 2
47252
47253            pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
47254          }
47255        } // the data section starts immediately after the PES header.
47256        // pes_header_data_length specifies the number of header bytes
47257        // that follow the last byte of the field.
47258
47259
47260        pes.data = payload.subarray(9 + payload[8]);
47261      },
47262
47263      /**
47264        * Pass completely parsed PES packets to the next stream in the pipeline
47265       **/
47266      flushStream = function flushStream(stream, type, forceFlush) {
47267        var packetData = new Uint8Array(stream.size),
47268            event = {
47269          type: type
47270        },
47271            i = 0,
47272            offset = 0,
47273            packetFlushable = false,
47274            fragment; // do nothing if there is not enough buffered data for a complete
47275        // PES header
47276
47277        if (!stream.data.length || stream.size < 9) {
47278          return;
47279        }
47280
47281        event.trackId = stream.data[0].pid; // reassemble the packet
47282
47283        for (i = 0; i < stream.data.length; i++) {
47284          fragment = stream.data[i];
47285          packetData.set(fragment.data, offset);
47286          offset += fragment.data.byteLength;
47287        } // parse assembled packet's PES header
47288
47289
47290        parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
47291        // check that there is enough stream data to fill the packet
47292
47293        packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
47294
47295        if (forceFlush || packetFlushable) {
47296          stream.size = 0;
47297          stream.data.length = 0;
47298        } // only emit packets that are complete. this is to avoid assembling
47299        // incomplete PES packets due to poor segmentation
47300
47301
47302        if (packetFlushable) {
47303          self.trigger('data', event);
47304        }
47305      };
47306
47307      _ElementaryStream.prototype.init.call(this);
47308      /**
47309       * Identifies M2TS packet types and parses PES packets using metadata
47310       * parsed from the PMT
47311       **/
47312
47313
47314      this.push = function (data) {
47315        ({
47316          pat: function pat() {// we have to wait for the PMT to arrive as well before we
47317            // have any meaningful metadata
47318          },
47319          pes: function pes() {
47320            var stream, streamType;
47321
47322            switch (data.streamType) {
47323              case streamTypes.H264_STREAM_TYPE:
47324                stream = video;
47325                streamType = 'video';
47326                break;
47327
47328              case streamTypes.ADTS_STREAM_TYPE:
47329                stream = audio;
47330                streamType = 'audio';
47331                break;
47332
47333              case streamTypes.METADATA_STREAM_TYPE:
47334                stream = timedMetadata;
47335                streamType = 'timed-metadata';
47336                break;
47337
47338              default:
47339                // ignore unknown stream types
47340                return;
47341            } // if a new packet is starting, we can flush the completed
47342            // packet
47343
47344
47345            if (data.payloadUnitStartIndicator) {
47346              flushStream(stream, streamType, true);
47347            } // buffer this fragment until we are sure we've received the
47348            // complete payload
47349
47350
47351            stream.data.push(data);
47352            stream.size += data.data.byteLength;
47353          },
47354          pmt: function pmt() {
47355            var event = {
47356              type: 'metadata',
47357              tracks: []
47358            };
47359            programMapTable = data.programMapTable; // translate audio and video streams to tracks
47360
47361            if (programMapTable.video !== null) {
47362              event.tracks.push({
47363                timelineStartInfo: {
47364                  baseMediaDecodeTime: 0
47365                },
47366                id: +programMapTable.video,
47367                codec: 'avc',
47368                type: 'video'
47369              });
47370            }
47371
47372            if (programMapTable.audio !== null) {
47373              event.tracks.push({
47374                timelineStartInfo: {
47375                  baseMediaDecodeTime: 0
47376                },
47377                id: +programMapTable.audio,
47378                codec: 'adts',
47379                type: 'audio'
47380              });
47381            }
47382
47383            segmentHadPmt = true;
47384            self.trigger('data', event);
47385          }
47386        })[data.type]();
47387      };
47388
47389      this.reset = function () {
47390        video.size = 0;
47391        video.data.length = 0;
47392        audio.size = 0;
47393        audio.data.length = 0;
47394        this.trigger('reset');
47395      };
47396      /**
47397       * Flush any remaining input. Video PES packets may be of variable
47398       * length. Normally, the start of a new video packet can trigger the
47399       * finalization of the previous packet. That is not possible if no
47400       * more video is forthcoming, however. In that case, some other
47401       * mechanism (like the end of the file) has to be employed. When it is
47402       * clear that no additional data is forthcoming, calling this method
47403       * will flush the buffered packets.
47404       */
47405
47406
47407      this.flushStreams_ = function () {
47408        // !!THIS ORDER IS IMPORTANT!!
47409        // video first then audio
47410        flushStream(video, 'video');
47411        flushStream(audio, 'audio');
47412        flushStream(timedMetadata, 'timed-metadata');
47413      };
47414
47415      this.flush = function () {
47416        // if on flush we haven't had a pmt emitted
47417        // and we have a pmt to emit. emit the pmt
47418        // so that we trigger a trackinfo downstream.
47419        if (!segmentHadPmt && programMapTable) {
47420          var pmt = {
47421            type: 'metadata',
47422            tracks: []
47423          }; // translate audio and video streams to tracks
47424
47425          if (programMapTable.video !== null) {
47426            pmt.tracks.push({
47427              timelineStartInfo: {
47428                baseMediaDecodeTime: 0
47429              },
47430              id: +programMapTable.video,
47431              codec: 'avc',
47432              type: 'video'
47433            });
47434          }
47435
47436          if (programMapTable.audio !== null) {
47437            pmt.tracks.push({
47438              timelineStartInfo: {
47439                baseMediaDecodeTime: 0
47440              },
47441              id: +programMapTable.audio,
47442              codec: 'adts',
47443              type: 'audio'
47444            });
47445          }
47446
47447          self.trigger('data', pmt);
47448        }
47449
47450        segmentHadPmt = false;
47451        this.flushStreams_();
47452        this.trigger('done');
47453      };
47454    };
47455
47456    _ElementaryStream.prototype = new stream();
47457    var m2ts = {
47458      PAT_PID: 0x0000,
47459      MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
vendor: 4,324 bytes, lines 47460-47573
47460      TransportPacketStream: _TransportPacketStream,
47461      TransportParseStream: _TransportParseStream,
47462      ElementaryStream: _ElementaryStream,
47463      TimestampRolloverStream: TimestampRolloverStream,
47464      CaptionStream: captionStream.CaptionStream,
47465      Cea608Stream: captionStream.Cea608Stream,
47466      Cea708Stream: captionStream.Cea708Stream,
47467      MetadataStream: metadataStream
47468    };
47469
47470    for (var type in streamTypes) {
47471      if (streamTypes.hasOwnProperty(type)) {
47472        m2ts[type] = streamTypes[type];
47473      }
47474    }
47475
47476    var m2ts_1 = m2ts;
47477    var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS;
47478
47479    var _AdtsStream;
47480
47481    var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
47482    /*
47483     * Accepts a ElementaryStream and emits data events with parsed
47484     * AAC Audio Frames of the individual packets. Input audio in ADTS
47485     * format is unpacked and re-emitted as AAC frames.
47486     *
47487     * @see http://wiki.multimedia.cx/index.php?title=ADTS
47488     * @see http://wiki.multimedia.cx/?title=Understanding_AAC
47489     */
47490
47491    _AdtsStream = function AdtsStream(handlePartialSegments) {
47492      var buffer,
47493          frameNum = 0;
47494
47495      _AdtsStream.prototype.init.call(this);
47496
47497      this.skipWarn_ = function (start, end) {
47498        this.trigger('log', {
47499          level: 'warn',
47500          message: "adts skiping bytes " + start + " to " + end + " in frame " + frameNum + " outside syncword"
47501        });
47502      };
47503
47504      this.push = function (packet) {
47505        var i = 0,
47506            frameLength,
47507            protectionSkipBytes,
47508            oldBuffer,
47509            sampleCount,
47510            adtsFrameDuration;
47511
47512        if (!handlePartialSegments) {
47513          frameNum = 0;
47514        }
47515
47516        if (packet.type !== 'audio') {
47517          // ignore non-audio data
47518          return;
47519        } // Prepend any data in the buffer to the input data so that we can parse
47520        // aac frames the cross a PES packet boundary
47521
47522
47523        if (buffer && buffer.length) {
47524          oldBuffer = buffer;
47525          buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
47526          buffer.set(oldBuffer);
47527          buffer.set(packet.data, oldBuffer.byteLength);
47528        } else {
47529          buffer = packet.data;
47530        } // unpack any ADTS frames which have been fully received
47531        // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
47532
47533
47534        var skip; // We use i + 7 here because we want to be able to parse the entire header.
47535        // If we don't have enough bytes to do that, then we definitely won't have a full frame.
47536
47537        while (i + 7 < buffer.length) {
47538          // Look for the start of an ADTS header..
47539          if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
47540            if (typeof skip !== 'number') {
47541              skip = i;
47542            } // If a valid header was not found,  jump one forward and attempt to
47543            // find a valid ADTS header starting at the next byte
47544
47545
47546            i++;
47547            continue;
47548          }
47549
47550          if (typeof skip === 'number') {
47551            this.skipWarn_(skip, i);
47552            skip = null;
47553          } // The protection skip bit tells us if we have 2 bytes of CRC data at the
47554          // end of the ADTS header
47555
47556
47557          protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
47558          // end of the sync sequence
47559          // NOTE: frame length includes the size of the header
47560
47561          frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
47562          sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
47563          adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
47564          // then we have to wait for more data
47565
47566          if (buffer.byteLength - i < frameLength) {
47567            break;
47568          } // Otherwise, deliver the complete AAC frame
47569
47570
47571          this.trigger('data', {
47572            pts: packet.pts + frameNum * adtsFrameDuration,
47573            dts: packet.dts + frameNum * adtsFrameDuration,
vendor: 10,322 bytes, lines 47574-47928
47574            sampleCount: sampleCount,
47575            audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
47576            channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
47577            samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
47578            samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
47579            // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
47580            samplesize: 16,
47581            // data is the frame without it's header
47582            data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
47583          });
47584          frameNum++;
47585          i += frameLength;
47586        }
47587
47588        if (typeof skip === 'number') {
47589          this.skipWarn_(skip, i);
47590          skip = null;
47591        } // remove processed bytes from the buffer.
47592
47593
47594        buffer = buffer.subarray(i);
47595      };
47596
47597      this.flush = function () {
47598        frameNum = 0;
47599        this.trigger('done');
47600      };
47601
47602      this.reset = function () {
47603        buffer = void 0;
47604        this.trigger('reset');
47605      };
47606
47607      this.endTimeline = function () {
47608        buffer = void 0;
47609        this.trigger('endedtimeline');
47610      };
47611    };
47612
47613    _AdtsStream.prototype = new stream();
47614    var adts = _AdtsStream;
47615    /**
47616     * mux.js
47617     *
47618     * Copyright (c) Brightcove
47619     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47620     */
47621
47622    var ExpGolomb;
47623    /**
47624     * Parser for exponential Golomb codes, a variable-bitwidth number encoding
47625     * scheme used by h264.
47626     */
47627
47628    ExpGolomb = function ExpGolomb(workingData) {
47629      var // the number of bytes left to examine in workingData
47630      workingBytesAvailable = workingData.byteLength,
47631          // the current word being examined
47632      workingWord = 0,
47633          // :uint
47634      // the number of bits left to examine in the current word
47635      workingBitsAvailable = 0; // :uint;
47636      // ():uint
47637
47638      this.length = function () {
47639        return 8 * workingBytesAvailable;
47640      }; // ():uint
47641
47642
47643      this.bitsAvailable = function () {
47644        return 8 * workingBytesAvailable + workingBitsAvailable;
47645      }; // ():void
47646
47647
47648      this.loadWord = function () {
47649        var position = workingData.byteLength - workingBytesAvailable,
47650            workingBytes = new Uint8Array(4),
47651            availableBytes = Math.min(4, workingBytesAvailable);
47652
47653        if (availableBytes === 0) {
47654          throw new Error('no bytes available');
47655        }
47656
47657        workingBytes.set(workingData.subarray(position, position + availableBytes));
47658        workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
47659
47660        workingBitsAvailable = availableBytes * 8;
47661        workingBytesAvailable -= availableBytes;
47662      }; // (count:int):void
47663
47664
47665      this.skipBits = function (count) {
47666        var skipBytes; // :int
47667
47668        if (workingBitsAvailable > count) {
47669          workingWord <<= count;
47670          workingBitsAvailable -= count;
47671        } else {
47672          count -= workingBitsAvailable;
47673          skipBytes = Math.floor(count / 8);
47674          count -= skipBytes * 8;
47675          workingBytesAvailable -= skipBytes;
47676          this.loadWord();
47677          workingWord <<= count;
47678          workingBitsAvailable -= count;
47679        }
47680      }; // (size:int):uint
47681
47682
47683      this.readBits = function (size) {
47684        var bits = Math.min(workingBitsAvailable, size),
47685            // :uint
47686        valu = workingWord >>> 32 - bits; // :uint
47687        // if size > 31, handle error
47688
47689        workingBitsAvailable -= bits;
47690
47691        if (workingBitsAvailable > 0) {
47692          workingWord <<= bits;
47693        } else if (workingBytesAvailable > 0) {
47694          this.loadWord();
47695        }
47696
47697        bits = size - bits;
47698
47699        if (bits > 0) {
47700          return valu << bits | this.readBits(bits);
47701        }
47702
47703        return valu;
47704      }; // ():uint
47705
47706
47707      this.skipLeadingZeros = function () {
47708        var leadingZeroCount; // :uint
47709
47710        for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
47711          if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
47712            // the first bit of working word is 1
47713            workingWord <<= leadingZeroCount;
47714            workingBitsAvailable -= leadingZeroCount;
47715            return leadingZeroCount;
47716          }
47717        } // we exhausted workingWord and still have not found a 1
47718
47719
47720        this.loadWord();
47721        return leadingZeroCount + this.skipLeadingZeros();
47722      }; // ():void
47723
47724
47725      this.skipUnsignedExpGolomb = function () {
47726        this.skipBits(1 + this.skipLeadingZeros());
47727      }; // ():void
47728
47729
47730      this.skipExpGolomb = function () {
47731        this.skipBits(1 + this.skipLeadingZeros());
47732      }; // ():uint
47733
47734
47735      this.readUnsignedExpGolomb = function () {
47736        var clz = this.skipLeadingZeros(); // :uint
47737
47738        return this.readBits(clz + 1) - 1;
47739      }; // ():int
47740
47741
47742      this.readExpGolomb = function () {
47743        var valu = this.readUnsignedExpGolomb(); // :int
47744
47745        if (0x01 & valu) {
47746          // the number is odd if the low order bit is set
47747          return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
47748        }
47749
47750        return -1 * (valu >>> 1); // divide by two then make it negative
47751      }; // Some convenience functions
47752      // :Boolean
47753
47754
47755      this.readBoolean = function () {
47756        return this.readBits(1) === 1;
47757      }; // ():int
47758
47759
47760      this.readUnsignedByte = function () {
47761        return this.readBits(8);
47762      };
47763
47764      this.loadWord();
47765    };
47766
47767    var expGolomb = ExpGolomb;
47768
47769    var _H264Stream, _NalByteStream;
47770
47771    var PROFILES_WITH_OPTIONAL_SPS_DATA;
47772    /**
47773     * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
47774     */
47775
47776    _NalByteStream = function NalByteStream() {
47777      var syncPoint = 0,
47778          i,
47779          buffer;
47780
47781      _NalByteStream.prototype.init.call(this);
47782      /*
47783       * Scans a byte stream and triggers a data event with the NAL units found.
47784       * @param {Object} data Event received from H264Stream
47785       * @param {Uint8Array} data.data The h264 byte stream to be scanned
47786       *
47787       * @see H264Stream.push
47788       */
47789
47790
47791      this.push = function (data) {
47792        var swapBuffer;
47793
47794        if (!buffer) {
47795          buffer = data.data;
47796        } else {
47797          swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
47798          swapBuffer.set(buffer);
47799          swapBuffer.set(data.data, buffer.byteLength);
47800          buffer = swapBuffer;
47801        }
47802
47803        var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
47804        // scan for NAL unit boundaries
47805        // a match looks like this:
47806        // 0 0 1 .. NAL .. 0 0 1
47807        // ^ sync point        ^ i
47808        // or this:
47809        // 0 0 1 .. NAL .. 0 0 0
47810        // ^ sync point        ^ i
47811        // advance the sync point to a NAL start, if necessary
47812
47813        for (; syncPoint < len - 3; syncPoint++) {
47814          if (buffer[syncPoint + 2] === 1) {
47815            // the sync point is properly aligned
47816            i = syncPoint + 5;
47817            break;
47818          }
47819        }
47820
47821        while (i < len) {
47822          // look at the current byte to determine if we've hit the end of
47823          // a NAL unit boundary
47824          switch (buffer[i]) {
47825            case 0:
47826              // skip past non-sync sequences
47827              if (buffer[i - 1] !== 0) {
47828                i += 2;
47829                break;
47830              } else if (buffer[i - 2] !== 0) {
47831                i++;
47832                break;
47833              } // deliver the NAL unit if it isn't empty
47834
47835
47836              if (syncPoint + 3 !== i - 2) {
47837                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47838              } // drop trailing zeroes
47839
47840
47841              do {
47842                i++;
47843              } while (buffer[i] !== 1 && i < len);
47844
47845              syncPoint = i - 2;
47846              i += 3;
47847              break;
47848
47849            case 1:
47850              // skip past non-sync sequences
47851              if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
47852                i += 3;
47853                break;
47854              } // deliver the NAL unit
47855
47856
47857              this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47858              syncPoint = i - 2;
47859              i += 3;
47860              break;
47861
47862            default:
47863              // the current byte isn't a one or zero, so it cannot be part
47864              // of a sync sequence
47865              i += 3;
47866              break;
47867          }
47868        } // filter out the NAL units that were delivered
47869
47870
47871        buffer = buffer.subarray(syncPoint);
47872        i -= syncPoint;
47873        syncPoint = 0;
47874      };
47875
47876      this.reset = function () {
47877        buffer = null;
47878        syncPoint = 0;
47879        this.trigger('reset');
47880      };
47881
47882      this.flush = function () {
47883        // deliver the last buffered NAL unit
47884        if (buffer && buffer.byteLength > 3) {
47885          this.trigger('data', buffer.subarray(syncPoint + 3));
47886        } // reset the stream state
47887
47888
47889        buffer = null;
47890        syncPoint = 0;
47891        this.trigger('done');
47892      };
47893
47894      this.endTimeline = function () {
47895        this.flush();
47896        this.trigger('endedtimeline');
47897      };
47898    };
47899
47900    _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
47901    // see Recommendation ITU-T H.264 (4/2013),
47902    // 7.3.2.1.1 Sequence parameter set data syntax
47903
47904    PROFILES_WITH_OPTIONAL_SPS_DATA = {
47905      100: true,
47906      110: true,
47907      122: true,
47908      244: true,
47909      44: true,
47910      83: true,
47911      86: true,
47912      118: true,
47913      128: true,
47914      // TODO: the three profiles below don't
47915      // appear to have sps data in the specificiation anymore?
47916      138: true,
47917      139: true,
47918      134: true
47919    };
47920    /**
47921     * Accepts input from a ElementaryStream and produces H.264 NAL unit data
47922     * events.
47923     */
47924
47925    _H264Stream = function H264Stream() {
47926      var nalByteStream = new _NalByteStream(),
47927          self,
47928          trackId,
47929          currentPts,
47930          currentDts,
47931          discardEmulationPreventionBytes,
47932          readSequenceParameterSet,
47933          skipScalingList;
47934
47935      _H264Stream.prototype.init.call(this);
47936
47937      self = this;
47938      /*
47939       * Pushes a packet from a stream onto the NalByteStream
47940       *
47941       * @param {Object} packet - A packet received from a stream
47942       * @param {Uint8Array} packet.data - The raw bytes of the packet
47943       * @param {Number} packet.dts - Decode timestamp of the packet
47944       * @param {Number} packet.pts - Presentation timestamp of the packet
47945       * @param {Number} packet.trackId - The id of the h264 track this packet came from
47946       * @param {('video'|'audio')} packet.type - The type of packet
47947       *
47948       */
47949
47950      this.push = function (packet) {
47951        if (packet.type !== 'video') {
47952          return;
47953        }
47954
47955        trackId = packet.trackId;
47956        currentPts = packet.pts;
47957        currentDts = packet.dts;
47958        nalByteStream.push(packet);
47959      };
47960      /*
47961       * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
47962       * for the NALUs to the next stream component.
47963       * Also, preprocess caption and sequence parameter NALUs.
47964       *
47965       * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
47966       * @see NalByteStream.push
47967       */
47968
47969
47970      nalByteStream.on('data', function (data) {
47971        var event = {
47972          trackId: trackId,
vendor: 28,582 bytes, lines 47973-48778
47973          pts: currentPts,
47974          dts: currentDts,
47975          data: data,
47976          nalUnitTypeCode: data[0] & 0x1f
47977        };
47978
47979        switch (event.nalUnitTypeCode) {
47980          case 0x05:
47981            event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
47982            break;
47983
47984          case 0x06:
47985            event.nalUnitType = 'sei_rbsp';
47986            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
47987            break;
47988
47989          case 0x07:
47990            event.nalUnitType = 'seq_parameter_set_rbsp';
47991            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
47992            event.config = readSequenceParameterSet(event.escapedRBSP);
47993            break;
47994
47995          case 0x08:
47996            event.nalUnitType = 'pic_parameter_set_rbsp';
47997            break;
47998
47999          case 0x09:
48000            event.nalUnitType = 'access_unit_delimiter_rbsp';
48001            break;
48002        } // This triggers data on the H264Stream
48003
48004
48005        self.trigger('data', event);
48006      });
48007      nalByteStream.on('done', function () {
48008        self.trigger('done');
48009      });
48010      nalByteStream.on('partialdone', function () {
48011        self.trigger('partialdone');
48012      });
48013      nalByteStream.on('reset', function () {
48014        self.trigger('reset');
48015      });
48016      nalByteStream.on('endedtimeline', function () {
48017        self.trigger('endedtimeline');
48018      });
48019
48020      this.flush = function () {
48021        nalByteStream.flush();
48022      };
48023
48024      this.partialFlush = function () {
48025        nalByteStream.partialFlush();
48026      };
48027
48028      this.reset = function () {
48029        nalByteStream.reset();
48030      };
48031
48032      this.endTimeline = function () {
48033        nalByteStream.endTimeline();
48034      };
48035      /**
48036       * Advance the ExpGolomb decoder past a scaling list. The scaling
48037       * list is optionally transmitted as part of a sequence parameter
48038       * set and is not relevant to transmuxing.
48039       * @param count {number} the number of entries in this scaling list
48040       * @param expGolombDecoder {object} an ExpGolomb pointed to the
48041       * start of a scaling list
48042       * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
48043       */
48044
48045
48046      skipScalingList = function skipScalingList(count, expGolombDecoder) {
48047        var lastScale = 8,
48048            nextScale = 8,
48049            j,
48050            deltaScale;
48051
48052        for (j = 0; j < count; j++) {
48053          if (nextScale !== 0) {
48054            deltaScale = expGolombDecoder.readExpGolomb();
48055            nextScale = (lastScale + deltaScale + 256) % 256;
48056          }
48057
48058          lastScale = nextScale === 0 ? lastScale : nextScale;
48059        }
48060      };
48061      /**
48062       * Expunge any "Emulation Prevention" bytes from a "Raw Byte
48063       * Sequence Payload"
48064       * @param data {Uint8Array} the bytes of a RBSP from a NAL
48065       * unit
48066       * @return {Uint8Array} the RBSP without any Emulation
48067       * Prevention Bytes
48068       */
48069
48070
48071      discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
48072        var length = data.byteLength,
48073            emulationPreventionBytesPositions = [],
48074            i = 1,
48075            newLength,
48076            newData; // Find all `Emulation Prevention Bytes`
48077
48078        while (i < length - 2) {
48079          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
48080            emulationPreventionBytesPositions.push(i + 2);
48081            i += 2;
48082          } else {
48083            i++;
48084          }
48085        } // If no Emulation Prevention Bytes were found just return the original
48086        // array
48087
48088
48089        if (emulationPreventionBytesPositions.length === 0) {
48090          return data;
48091        } // Create a new array to hold the NAL unit data
48092
48093
48094        newLength = length - emulationPreventionBytesPositions.length;
48095        newData = new Uint8Array(newLength);
48096        var sourceIndex = 0;
48097
48098        for (i = 0; i < newLength; sourceIndex++, i++) {
48099          if (sourceIndex === emulationPreventionBytesPositions[0]) {
48100            // Skip this byte
48101            sourceIndex++; // Remove this position index
48102
48103            emulationPreventionBytesPositions.shift();
48104          }
48105
48106          newData[i] = data[sourceIndex];
48107        }
48108
48109        return newData;
48110      };
48111      /**
48112       * Read a sequence parameter set and return some interesting video
48113       * properties. A sequence parameter set is the H264 metadata that
48114       * describes the properties of upcoming video frames.
48115       * @param data {Uint8Array} the bytes of a sequence parameter set
48116       * @return {object} an object with configuration parsed from the
48117       * sequence parameter set, including the dimensions of the
48118       * associated video frames.
48119       */
48120
48121
48122      readSequenceParameterSet = function readSequenceParameterSet(data) {
48123        var frameCropLeftOffset = 0,
48124            frameCropRightOffset = 0,
48125            frameCropTopOffset = 0,
48126            frameCropBottomOffset = 0,
48127            expGolombDecoder,
48128            profileIdc,
48129            levelIdc,
48130            profileCompatibility,
48131            chromaFormatIdc,
48132            picOrderCntType,
48133            numRefFramesInPicOrderCntCycle,
48134            picWidthInMbsMinus1,
48135            picHeightInMapUnitsMinus1,
48136            frameMbsOnlyFlag,
48137            scalingListCount,
48138            sarRatio = [1, 1],
48139            aspectRatioIdc,
48140            i;
48141        expGolombDecoder = new expGolomb(data);
48142        profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
48143
48144        profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
48145
48146        levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
48147
48148        expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
48149        // some profiles have more optional data we don't need
48150
48151        if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
48152          chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
48153
48154          if (chromaFormatIdc === 3) {
48155            expGolombDecoder.skipBits(1); // separate_colour_plane_flag
48156          }
48157
48158          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
48159
48160          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
48161
48162          expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
48163
48164          if (expGolombDecoder.readBoolean()) {
48165            // seq_scaling_matrix_present_flag
48166            scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
48167
48168            for (i = 0; i < scalingListCount; i++) {
48169              if (expGolombDecoder.readBoolean()) {
48170                // seq_scaling_list_present_flag[ i ]
48171                if (i < 6) {
48172                  skipScalingList(16, expGolombDecoder);
48173                } else {
48174                  skipScalingList(64, expGolombDecoder);
48175                }
48176              }
48177            }
48178          }
48179        }
48180
48181        expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
48182
48183        picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
48184
48185        if (picOrderCntType === 0) {
48186          expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
48187        } else if (picOrderCntType === 1) {
48188          expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
48189
48190          expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
48191
48192          expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
48193
48194          numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
48195
48196          for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
48197            expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
48198          }
48199        }
48200
48201        expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
48202
48203        expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
48204
48205        picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48206        picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48207        frameMbsOnlyFlag = expGolombDecoder.readBits(1);
48208
48209        if (frameMbsOnlyFlag === 0) {
48210          expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
48211        }
48212
48213        expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
48214
48215        if (expGolombDecoder.readBoolean()) {
48216          // frame_cropping_flag
48217          frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
48218          frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
48219          frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
48220          frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
48221        }
48222
48223        if (expGolombDecoder.readBoolean()) {
48224          // vui_parameters_present_flag
48225          if (expGolombDecoder.readBoolean()) {
48226            // aspect_ratio_info_present_flag
48227            aspectRatioIdc = expGolombDecoder.readUnsignedByte();
48228
48229            switch (aspectRatioIdc) {
48230              case 1:
48231                sarRatio = [1, 1];
48232                break;
48233
48234              case 2:
48235                sarRatio = [12, 11];
48236                break;
48237
48238              case 3:
48239                sarRatio = [10, 11];
48240                break;
48241
48242              case 4:
48243                sarRatio = [16, 11];
48244                break;
48245
48246              case 5:
48247                sarRatio = [40, 33];
48248                break;
48249
48250              case 6:
48251                sarRatio = [24, 11];
48252                break;
48253
48254              case 7:
48255                sarRatio = [20, 11];
48256                break;
48257
48258              case 8:
48259                sarRatio = [32, 11];
48260                break;
48261
48262              case 9:
48263                sarRatio = [80, 33];
48264                break;
48265
48266              case 10:
48267                sarRatio = [18, 11];
48268                break;
48269
48270              case 11:
48271                sarRatio = [15, 11];
48272                break;
48273
48274              case 12:
48275                sarRatio = [64, 33];
48276                break;
48277
48278              case 13:
48279                sarRatio = [160, 99];
48280                break;
48281
48282              case 14:
48283                sarRatio = [4, 3];
48284                break;
48285
48286              case 15:
48287                sarRatio = [3, 2];
48288                break;
48289
48290              case 16:
48291                sarRatio = [2, 1];
48292                break;
48293
48294              case 255:
48295                {
48296                  sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
48297                  break;
48298                }
48299            }
48300
48301            if (sarRatio) {
48302              sarRatio[0] / sarRatio[1];
48303            }
48304          }
48305        }
48306
48307        return {
48308          profileIdc: profileIdc,
48309          levelIdc: levelIdc,
48310          profileCompatibility: profileCompatibility,
48311          width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
48312          height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
48313          // sar is sample aspect ratio
48314          sarRatio: sarRatio
48315        };
48316      };
48317    };
48318
48319    _H264Stream.prototype = new stream();
48320    var h264 = {
48321      H264Stream: _H264Stream,
48322      NalByteStream: _NalByteStream
48323    };
48324    /**
48325     * mux.js
48326     *
48327     * Copyright (c) Brightcove
48328     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48329     *
48330     * Utilities to detect basic properties and metadata about Aac data.
48331     */
48332
48333    var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
48334
48335    var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
48336      var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
48337          flags = header[byteIndex + 5],
48338          footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
48339
48340      returnSize = returnSize >= 0 ? returnSize : 0;
48341
48342      if (footerPresent) {
48343        return returnSize + 20;
48344      }
48345
48346      return returnSize + 10;
48347    };
48348
48349    var getId3Offset = function getId3Offset(data, offset) {
48350      if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
48351        return offset;
48352      }
48353
48354      offset += parseId3TagSize(data, offset);
48355      return getId3Offset(data, offset);
48356    }; // TODO: use vhs-utils
48357
48358
48359    var isLikelyAacData$1 = function isLikelyAacData(data) {
48360      var offset = getId3Offset(data, 0);
48361      return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this
48362      // is not mp3 data but aac data.
48363      (data[offset + 1] & 0x16) === 0x10;
48364    };
48365
48366    var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
48367      return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
48368    }; // return a percent-encoded representation of the specified byte range
48369    // @see http://en.wikipedia.org/wiki/Percent-encoding
48370
48371
48372    var percentEncode = function percentEncode(bytes, start, end) {
48373      var i,
48374          result = '';
48375
48376      for (i = start; i < end; i++) {
48377        result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
48378      }
48379
48380      return result;
48381    }; // return the string representation of the specified byte range,
48382    // interpreted as ISO-8859-1.
48383
48384
48385    var parseIso88591 = function parseIso88591(bytes, start, end) {
48386      return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
48387    };
48388
48389    var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
48390      var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
48391          middle = header[byteIndex + 4] << 3,
48392          highTwo = header[byteIndex + 3] & 0x3 << 11;
48393      return highTwo | middle | lowThree;
48394    };
48395
48396    var parseType$2 = function parseType(header, byteIndex) {
48397      if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
48398        return 'timed-metadata';
48399      } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
48400        return 'audio';
48401      }
48402
48403      return null;
48404    };
48405
48406    var parseSampleRate = function parseSampleRate(packet) {
48407      var i = 0;
48408
48409      while (i + 5 < packet.length) {
48410        if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
48411          // If a valid header was not found,  jump one forward and attempt to
48412          // find a valid ADTS header starting at the next byte
48413          i++;
48414          continue;
48415        }
48416
48417        return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
48418      }
48419
48420      return null;
48421    };
48422
48423    var parseAacTimestamp = function parseAacTimestamp(packet) {
48424      var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
48425
48426      frameStart = 10;
48427
48428      if (packet[5] & 0x40) {
48429        // advance the frame start past the extended header
48430        frameStart += 4; // header size field
48431
48432        frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
48433      } // parse one or more ID3 frames
48434      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
48435
48436
48437      do {
48438        // determine the number of bytes in this frame
48439        frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
48440
48441        if (frameSize < 1) {
48442          return null;
48443        }
48444
48445        frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
48446
48447        if (frameHeader === 'PRIV') {
48448          frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
48449
48450          for (var i = 0; i < frame.byteLength; i++) {
48451            if (frame[i] === 0) {
48452              var owner = parseIso88591(frame, 0, i);
48453
48454              if (owner === 'com.apple.streaming.transportStreamTimestamp') {
48455                var d = frame.subarray(i + 1);
48456                var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
48457                size *= 4;
48458                size += d[7] & 0x03;
48459                return size;
48460              }
48461
48462              break;
48463            }
48464          }
48465        }
48466
48467        frameStart += 10; // advance past the frame header
48468
48469        frameStart += frameSize; // advance past the frame body
48470      } while (frameStart < packet.byteLength);
48471
48472      return null;
48473    };
48474
48475    var utils = {
48476      isLikelyAacData: isLikelyAacData$1,
48477      parseId3TagSize: parseId3TagSize,
48478      parseAdtsSize: parseAdtsSize,
48479      parseType: parseType$2,
48480      parseSampleRate: parseSampleRate,
48481      parseAacTimestamp: parseAacTimestamp
48482    };
48483
48484    var _AacStream;
48485    /**
48486     * Splits an incoming stream of binary data into ADTS and ID3 Frames.
48487     */
48488
48489
48490    _AacStream = function AacStream() {
48491      var everything = new Uint8Array(),
48492          timeStamp = 0;
48493
48494      _AacStream.prototype.init.call(this);
48495
48496      this.setTimestamp = function (timestamp) {
48497        timeStamp = timestamp;
48498      };
48499
48500      this.push = function (bytes) {
48501        var frameSize = 0,
48502            byteIndex = 0,
48503            bytesLeft,
48504            chunk,
48505            packet,
48506            tempLength; // If there are bytes remaining from the last segment, prepend them to the
48507        // bytes that were pushed in
48508
48509        if (everything.length) {
48510          tempLength = everything.length;
48511          everything = new Uint8Array(bytes.byteLength + tempLength);
48512          everything.set(everything.subarray(0, tempLength));
48513          everything.set(bytes, tempLength);
48514        } else {
48515          everything = bytes;
48516        }
48517
48518        while (everything.length - byteIndex >= 3) {
48519          if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
48520            // Exit early because we don't have enough to parse
48521            // the ID3 tag header
48522            if (everything.length - byteIndex < 10) {
48523              break;
48524            } // check framesize
48525
48526
48527            frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48528            // to emit a full packet
48529            // Add to byteIndex to support multiple ID3 tags in sequence
48530
48531            if (byteIndex + frameSize > everything.length) {
48532              break;
48533            }
48534
48535            chunk = {
48536              type: 'timed-metadata',
48537              data: everything.subarray(byteIndex, byteIndex + frameSize)
48538            };
48539            this.trigger('data', chunk);
48540            byteIndex += frameSize;
48541            continue;
48542          } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
48543            // Exit early because we don't have enough to parse
48544            // the ADTS frame header
48545            if (everything.length - byteIndex < 7) {
48546              break;
48547            }
48548
48549            frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48550            // to emit a full packet
48551
48552            if (byteIndex + frameSize > everything.length) {
48553              break;
48554            }
48555
48556            packet = {
48557              type: 'audio',
48558              data: everything.subarray(byteIndex, byteIndex + frameSize),
48559              pts: timeStamp,
48560              dts: timeStamp
48561            };
48562            this.trigger('data', packet);
48563            byteIndex += frameSize;
48564            continue;
48565          }
48566
48567          byteIndex++;
48568        }
48569
48570        bytesLeft = everything.length - byteIndex;
48571
48572        if (bytesLeft > 0) {
48573          everything = everything.subarray(byteIndex);
48574        } else {
48575          everything = new Uint8Array();
48576        }
48577      };
48578
48579      this.reset = function () {
48580        everything = new Uint8Array();
48581        this.trigger('reset');
48582      };
48583
48584      this.endTimeline = function () {
48585        everything = new Uint8Array();
48586        this.trigger('endedtimeline');
48587      };
48588    };
48589
48590    _AacStream.prototype = new stream();
48591    var aac = _AacStream; // constants
48592
48593    var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
48594    var audioProperties = AUDIO_PROPERTIES;
48595    var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
48596    var videoProperties = VIDEO_PROPERTIES;
48597    var H264Stream = h264.H264Stream;
48598    var isLikelyAacData = utils.isLikelyAacData;
48599    var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; // object types
48600
48601    var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
48602    /**
48603     * Compare two arrays (even typed) for same-ness
48604     */
48605
48606
48607    var arrayEquals = function arrayEquals(a, b) {
48608      var i;
48609
48610      if (a.length !== b.length) {
48611        return false;
48612      } // compare the value of each element in the array
48613
48614
48615      for (i = 0; i < a.length; i++) {
48616        if (a[i] !== b[i]) {
48617          return false;
48618        }
48619      }
48620
48621      return true;
48622    };
48623
48624    var generateSegmentTimingInfo = function generateSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
48625      var ptsOffsetFromDts = startPts - startDts,
48626          decodeDuration = endDts - startDts,
48627          presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
48628      // however, the player time values will reflect a start from the baseMediaDecodeTime.
48629      // In order to provide relevant values for the player times, base timing info on the
48630      // baseMediaDecodeTime and the DTS and PTS durations of the segment.
48631
48632      return {
48633        start: {
48634          dts: baseMediaDecodeTime,
48635          pts: baseMediaDecodeTime + ptsOffsetFromDts
48636        },
48637        end: {
48638          dts: baseMediaDecodeTime + decodeDuration,
48639          pts: baseMediaDecodeTime + presentationDuration
48640        },
48641        prependedContentDuration: prependedContentDuration,
48642        baseMediaDecodeTime: baseMediaDecodeTime
48643      };
48644    };
48645    /**
48646     * Constructs a single-track, ISO BMFF media segment from AAC data
48647     * events. The output of this stream can be fed to a SourceBuffer
48648     * configured with a suitable initialization segment.
48649     * @param track {object} track metadata configuration
48650     * @param options {object} transmuxer options object
48651     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48652     *        in the source; false to adjust the first segment to start at 0.
48653     */
48654
48655
48656    _AudioSegmentStream = function AudioSegmentStream(track, options) {
48657      var adtsFrames = [],
48658          sequenceNumber,
48659          earliestAllowedDts = 0,
48660          audioAppendStartTs = 0,
48661          videoBaseMediaDecodeTime = Infinity;
48662      options = options || {};
48663      sequenceNumber = options.firstSequenceNumber || 0;
48664
48665      _AudioSegmentStream.prototype.init.call(this);
48666
48667      this.push = function (data) {
48668        trackDecodeInfo.collectDtsInfo(track, data);
48669
48670        if (track) {
48671          audioProperties.forEach(function (prop) {
48672            track[prop] = data[prop];
48673          });
48674        } // buffer audio data until end() is called
48675
48676
48677        adtsFrames.push(data);
48678      };
48679
48680      this.setEarliestDts = function (earliestDts) {
48681        earliestAllowedDts = earliestDts;
48682      };
48683
48684      this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
48685        videoBaseMediaDecodeTime = baseMediaDecodeTime;
48686      };
48687
48688      this.setAudioAppendStart = function (timestamp) {
48689        audioAppendStartTs = timestamp;
48690      };
48691
48692      this.flush = function () {
48693        var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
48694
48695        if (adtsFrames.length === 0) {
48696          this.trigger('done', 'AudioSegmentStream');
48697          return;
48698        }
48699
48700        frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
48701        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
48702
48703        videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
48704        // samples (that is, adts frames) in the audio data
48705
48706        track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
48707
48708        mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
48709        adtsFrames = [];
48710        moof = mp4Generator.moof(sequenceNumber, [track]);
48711        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
48712
48713        sequenceNumber++;
48714        boxes.set(moof);
48715        boxes.set(mdat, moof.byteLength);
48716        trackDecodeInfo.clearDtsInfo(track);
48717        frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
48718        // tests) on adding the timingInfo event. However, it seems unlikely that there's a
48719        // valid use-case where an init segment/data should be triggered without associated
48720        // frames. Leaving for now, but should be looked into.
48721
48722        if (frames.length) {
48723          segmentDuration = frames.length * frameDuration;
48724          this.trigger('segmentTimingInfo', generateSegmentTimingInfo( // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
48725          // frame info is in video clock cycles. Convert to match expectation of
48726          // listeners (that all timestamps will be based on video clock cycles).
48727          clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate), // frame times are already in video clock, as is segment duration
48728          frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
48729          this.trigger('timingInfo', {
48730            start: frames[0].pts,
48731            end: frames[0].pts + segmentDuration
48732          });
48733        }
48734
48735        this.trigger('data', {
48736          track: track,
48737          boxes: boxes
48738        });
48739        this.trigger('done', 'AudioSegmentStream');
48740      };
48741
48742      this.reset = function () {
48743        trackDecodeInfo.clearDtsInfo(track);
48744        adtsFrames = [];
48745        this.trigger('reset');
48746      };
48747    };
48748
48749    _AudioSegmentStream.prototype = new stream();
48750    /**
48751     * Constructs a single-track, ISO BMFF media segment from H264 data
48752     * events. The output of this stream can be fed to a SourceBuffer
48753     * configured with a suitable initialization segment.
48754     * @param track {object} track metadata configuration
48755     * @param options {object} transmuxer options object
48756     * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
48757     *        gopsToAlignWith list when attempting to align gop pts
48758     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48759     *        in the source; false to adjust the first segment to start at 0.
48760     */
48761
48762    _VideoSegmentStream = function VideoSegmentStream(track, options) {
48763      var sequenceNumber,
48764          nalUnits = [],
48765          gopsToAlignWith = [],
48766          config,
48767          pps;
48768      options = options || {};
48769      sequenceNumber = options.firstSequenceNumber || 0;
48770
48771      _VideoSegmentStream.prototype.init.call(this);
48772
48773      delete track.minPTS;
48774      this.gopCache_ = [];
48775      /**
48776        * Constructs a ISO BMFF segment given H264 nalUnits
48777        * @param {Object} nalUnit A data event representing a nalUnit
48778        * @param {String} nalUnit.nalUnitType
48779        * @param {Object} nalUnit.config Properties for a mp4 track
48780        * @param {Uint8Array} nalUnit.data The nalUnit bytes
48781        * @see lib/codecs/h264.js
48782       **/
48783
48784      this.push = function (nalUnit) {
48785        trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
48786
48787        if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
48788          config = nalUnit.config;
48789          track.sps = [nalUnit.data];
48790          videoProperties.forEach(function (prop) {
48791            track[prop] = config[prop];
48792          }, this);
48793        }
48794
48795        if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
48796          pps = nalUnit.data;
48797          track.pps = [nalUnit.data];
48798        } // buffer video until flush() is called
48799
48800
48801        nalUnits.push(nalUnit);
48802      };
48803      /**
48804        * Pass constructed ISO BMFF track and boxes on to the
48805        * next stream in the pipeline
48806       **/
48807
48808
48809      this.flush = function () {
48810        var frames,
48811            gopForFusion,
48812            gops,
48813            moof,
48814            mdat,
48815            boxes,
48816            prependedContentDuration = 0,
48817            firstGop,
48818            lastGop; // Throw away nalUnits at the start of the byte stream until
48819        // we find the first AUD
48820
48821        while (nalUnits.length) {
48822          if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
48823            break;
48824          }
48825
48826          nalUnits.shift();
48827        } // Return early if no video data has been observed
48828
48829
48830        if (nalUnits.length === 0) {
48831          this.resetStream_();
48832          this.trigger('done', 'VideoSegmentStream');
48833          return;
48834        } // Organize the raw nal-units into arrays that represent
48835        // higher-level constructs such as frames and gops
48836        // (group-of-pictures)
48837
48838
48839        frames = frameUtils.groupNalsIntoFrames(nalUnits);
48840        gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
48841        // a problem since MSE (on Chrome) requires a leading keyframe.
48842        //
48843        // We have two approaches to repairing this situation:
48844        // 1) GOP-FUSION:
48845        //    This is where we keep track of the GOPS (group-of-pictures)
48846        //    from previous fragments and attempt to find one that we can
48847        //    prepend to the current fragment in order to create a valid
48848        //    fragment.
48849        // 2) KEYFRAME-PULLING:
48850        //    Here we search for the first keyframe in the fragment and
48851        //    throw away all the frames between the start of the fragment
48852        //    and that keyframe. We then extend the duration and pull the
48853        //    PTS of the keyframe forward so that it covers the time range
48854        //    of the frames that were disposed of.
48855        //
48856        // #1 is far prefereable over #2 which can cause "stuttering" but
48857        // requires more things to be just right.
48858
48859        if (!gops[0][0].keyFrame) {
48860          // Search for a gop for fusion from our gopCache
48861          gopForFusion = this.getGopForFusion_(nalUnits[0], track);
48862
48863          if (gopForFusion) {
48864            // in order to provide more accurate timing information about the segment, save
48865            // the number of seconds prepended to the original segment due to GOP fusion
48866            prependedContentDuration = gopForFusion.duration;
48867            gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
48868            // new gop at the beginning
48869
48870            gops.byteLength += gopForFusion.byteLength;
48871            gops.nalCount += gopForFusion.nalCount;
48872            gops.pts = gopForFusion.pts;
48873            gops.dts = gopForFusion.dts;
48874            gops.duration += gopForFusion.duration;
48875          } else {
48876            // If we didn't find a candidate gop fall back to keyframe-pulling
48877            gops = frameUtils.extendFirstKeyFrame(gops);
48878          }
48879        } // Trim gops to align with gopsToAlignWith
48880
48881
48882        if (gopsToAlignWith.length) {
48883          var alignedGops;
48884
48885          if (options.alignGopsAtEnd) {
48886            alignedGops = this.alignGopsAtEnd_(gops);
48887          } else {
48888            alignedGops = this.alignGopsAtStart_(gops);
48889          }
48890
48891          if (!alignedGops) {
48892            // save all the nals in the last GOP into the gop cache
48893            this.gopCache_.unshift({
48894              gop: gops.pop(),
48895              pps: track.pps,
48896              sps: track.sps
48897            }); // Keep a maximum of 6 GOPs in the cache
48898
48899            this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48900
48901            nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
48902
vendor: 7,515 bytes, lines 48903-49102
48903            this.resetStream_();
48904            this.trigger('done', 'VideoSegmentStream');
48905            return;
48906          } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
48907          // when recalculated before sending off to CoalesceStream
48908
48909
48910          trackDecodeInfo.clearDtsInfo(track);
48911          gops = alignedGops;
48912        }
48913
48914        trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
48915        // samples (that is, frames) in the video data
48916
48917        track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
48918
48919        mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
48920        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
48921        this.trigger('processedGopsInfo', gops.map(function (gop) {
48922          return {
48923            pts: gop.pts,
48924            dts: gop.dts,
48925            byteLength: gop.byteLength
48926          };
48927        }));
48928        firstGop = gops[0];
48929        lastGop = gops[gops.length - 1];
48930        this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
48931        this.trigger('timingInfo', {
48932          start: gops[0].pts,
48933          end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
48934        }); // save all the nals in the last GOP into the gop cache
48935
48936        this.gopCache_.unshift({
48937          gop: gops.pop(),
48938          pps: track.pps,
48939          sps: track.sps
48940        }); // Keep a maximum of 6 GOPs in the cache
48941
48942        this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48943
48944        nalUnits = [];
48945        this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
48946        this.trigger('timelineStartInfo', track.timelineStartInfo);
48947        moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
48948        // throwing away hundreds of media segment fragments
48949
48950        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
48951
48952        sequenceNumber++;
48953        boxes.set(moof);
48954        boxes.set(mdat, moof.byteLength);
48955        this.trigger('data', {
48956          track: track,
48957          boxes: boxes
48958        });
48959        this.resetStream_(); // Continue with the flush process now
48960
48961        this.trigger('done', 'VideoSegmentStream');
48962      };
48963
48964      this.reset = function () {
48965        this.resetStream_();
48966        nalUnits = [];
48967        this.gopCache_.length = 0;
48968        gopsToAlignWith.length = 0;
48969        this.trigger('reset');
48970      };
48971
48972      this.resetStream_ = function () {
48973        trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
48974        // for instance, when we are rendition switching
48975
48976        config = undefined;
48977        pps = undefined;
48978      }; // Search for a candidate Gop for gop-fusion from the gop cache and
48979      // return it or return null if no good candidate was found
48980
48981
48982      this.getGopForFusion_ = function (nalUnit) {
48983        var halfSecond = 45000,
48984            // Half-a-second in a 90khz clock
48985        allowableOverlap = 10000,
48986            // About 3 frames @ 30fps
48987        nearestDistance = Infinity,
48988            dtsDistance,
48989            nearestGopObj,
48990            currentGop,
48991            currentGopObj,
48992            i; // Search for the GOP nearest to the beginning of this nal unit
48993
48994        for (i = 0; i < this.gopCache_.length; i++) {
48995          currentGopObj = this.gopCache_[i];
48996          currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
48997
48998          if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
48999            continue;
49000          } // Reject Gops that would require a negative baseMediaDecodeTime
49001
49002
49003          if (currentGop.dts < track.timelineStartInfo.dts) {
49004            continue;
49005          } // The distance between the end of the gop and the start of the nalUnit
49006
49007
49008          dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
49009          // a half-second of the nal unit
49010
49011          if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
49012            // Always use the closest GOP we found if there is more than
49013            // one candidate
49014            if (!nearestGopObj || nearestDistance > dtsDistance) {
49015              nearestGopObj = currentGopObj;
49016              nearestDistance = dtsDistance;
49017            }
49018          }
49019        }
49020
49021        if (nearestGopObj) {
49022          return nearestGopObj.gop;
49023        }
49024
49025        return null;
49026      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
49027      // of gopsToAlignWith starting from the START of the list
49028
49029
49030      this.alignGopsAtStart_ = function (gops) {
49031        var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
49032        byteLength = gops.byteLength;
49033        nalCount = gops.nalCount;
49034        duration = gops.duration;
49035        alignIndex = gopIndex = 0;
49036
49037        while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
49038          align = gopsToAlignWith[alignIndex];
49039          gop = gops[gopIndex];
49040
49041          if (align.pts === gop.pts) {
49042            break;
49043          }
49044
49045          if (gop.pts > align.pts) {
49046            // this current gop starts after the current gop we want to align on, so increment
49047            // align index
49048            alignIndex++;
49049            continue;
49050          } // current gop starts before the current gop we want to align on. so increment gop
49051          // index
49052
49053
49054          gopIndex++;
49055          byteLength -= gop.byteLength;
49056          nalCount -= gop.nalCount;
49057          duration -= gop.duration;
49058        }
49059
49060        if (gopIndex === 0) {
49061          // no gops to trim
49062          return gops;
49063        }
49064
49065        if (gopIndex === gops.length) {
49066          // all gops trimmed, skip appending all gops
49067          return null;
49068        }
49069
49070        alignedGops = gops.slice(gopIndex);
49071        alignedGops.byteLength = byteLength;
49072        alignedGops.duration = duration;
49073        alignedGops.nalCount = nalCount;
49074        alignedGops.pts = alignedGops[0].pts;
49075        alignedGops.dts = alignedGops[0].dts;
49076        return alignedGops;
49077      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
49078      // of gopsToAlignWith starting from the END of the list
49079
49080
49081      this.alignGopsAtEnd_ = function (gops) {
49082        var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
49083        alignIndex = gopsToAlignWith.length - 1;
49084        gopIndex = gops.length - 1;
49085        alignEndIndex = null;
49086        matchFound = false;
49087
49088        while (alignIndex >= 0 && gopIndex >= 0) {
49089          align = gopsToAlignWith[alignIndex];
49090          gop = gops[gopIndex];
49091
49092          if (align.pts === gop.pts) {
49093            matchFound = true;
49094            break;
49095          }
49096
49097          if (align.pts > gop.pts) {
49098            alignIndex--;
49099            continue;
49100          }
49101
49102          if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 4,348 bytes, lines 49103-49233
49103            // gop.pts is greater than the last alignment candidate. If no match is found
49104            // by the end of this loop, we still want to append gops that come after this
49105            // point
49106            alignEndIndex = gopIndex;
49107          }
49108
49109          gopIndex--;
49110        }
49111
49112        if (!matchFound && alignEndIndex === null) {
49113          return null;
49114        }
49115
49116        var trimIndex;
49117
49118        if (matchFound) {
49119          trimIndex = gopIndex;
49120        } else {
49121          trimIndex = alignEndIndex;
49122        }
49123
49124        if (trimIndex === 0) {
49125          return gops;
49126        }
49127
49128        var alignedGops = gops.slice(trimIndex);
49129        var metadata = alignedGops.reduce(function (total, gop) {
49130          total.byteLength += gop.byteLength;
49131          total.duration += gop.duration;
49132          total.nalCount += gop.nalCount;
49133          return total;
49134        }, {
49135          byteLength: 0,
49136          duration: 0,
49137          nalCount: 0
49138        });
49139        alignedGops.byteLength = metadata.byteLength;
49140        alignedGops.duration = metadata.duration;
49141        alignedGops.nalCount = metadata.nalCount;
49142        alignedGops.pts = alignedGops[0].pts;
49143        alignedGops.dts = alignedGops[0].dts;
49144        return alignedGops;
49145      };
49146
49147      this.alignGopsWith = function (newGopsToAlignWith) {
49148        gopsToAlignWith = newGopsToAlignWith;
49149      };
49150    };
49151
49152    _VideoSegmentStream.prototype = new stream();
49153    /**
49154     * A Stream that can combine multiple streams (ie. audio & video)
49155     * into a single output segment for MSE. Also supports audio-only
49156     * and video-only streams.
49157     * @param options {object} transmuxer options object
49158     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
49159     *        in the source; false to adjust the first segment to start at media timeline start.
49160     */
49161
49162    _CoalesceStream = function CoalesceStream(options, metadataStream) {
49163      // Number of Tracks per output segment
49164      // If greater than 1, we combine multiple
49165      // tracks into a single segment
49166      this.numberOfTracks = 0;
49167      this.metadataStream = metadataStream;
49168      options = options || {};
49169
49170      if (typeof options.remux !== 'undefined') {
49171        this.remuxTracks = !!options.remux;
49172      } else {
49173        this.remuxTracks = true;
49174      }
49175
49176      if (typeof options.keepOriginalTimestamps === 'boolean') {
49177        this.keepOriginalTimestamps = options.keepOriginalTimestamps;
49178      } else {
49179        this.keepOriginalTimestamps = false;
49180      }
49181
49182      this.pendingTracks = [];
49183      this.videoTrack = null;
49184      this.pendingBoxes = [];
49185      this.pendingCaptions = [];
49186      this.pendingMetadata = [];
49187      this.pendingBytes = 0;
49188      this.emittedTracks = 0;
49189
49190      _CoalesceStream.prototype.init.call(this); // Take output from multiple
49191
49192
49193      this.push = function (output) {
49194        // buffer incoming captions until the associated video segment
49195        // finishes
49196        if (output.text) {
49197          return this.pendingCaptions.push(output);
49198        } // buffer incoming id3 tags until the final flush
49199
49200
49201        if (output.frames) {
49202          return this.pendingMetadata.push(output);
49203        } // Add this track to the list of pending tracks and store
49204        // important information required for the construction of
49205        // the final segment
49206
49207
49208        this.pendingTracks.push(output.track);
49209        this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
49210        // We unshift audio and push video because
49211        // as of Chrome 75 when switching from
49212        // one init segment to another if the video
49213        // mdat does not appear after the audio mdat
49214        // only audio will play for the duration of our transmux.
49215
49216        if (output.track.type === 'video') {
49217          this.videoTrack = output.track;
49218          this.pendingBoxes.push(output.boxes);
49219        }
49220
49221        if (output.track.type === 'audio') {
49222          this.audioTrack = output.track;
49223          this.pendingBoxes.unshift(output.boxes);
49224        }
49225      };
49226    };
49227
49228    _CoalesceStream.prototype = new stream();
49229
49230    _CoalesceStream.prototype.flush = function (flushSource) {
49231      var offset = 0,
49232          event = {
49233        captions: [],
vendor: 4,652 bytes, lines 49234-49339
49234        captionStreams: {},
49235        metadata: [],
49236        info: {}
49237      },
49238          caption,
49239          id3,
49240          initSegment,
49241          timelineStartPts = 0,
49242          i;
49243
49244      if (this.pendingTracks.length < this.numberOfTracks) {
49245        if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
49246          // Return because we haven't received a flush from a data-generating
49247          // portion of the segment (meaning that we have only recieved meta-data
49248          // or captions.)
49249          return;
49250        } else if (this.remuxTracks) {
49251          // Return until we have enough tracks from the pipeline to remux (if we
49252          // are remuxing audio and video into a single MP4)
49253          return;
49254        } else if (this.pendingTracks.length === 0) {
49255          // In the case where we receive a flush without any data having been
49256          // received we consider it an emitted track for the purposes of coalescing
49257          // `done` events.
49258          // We do this for the case where there is an audio and video track in the
49259          // segment but no audio data. (seen in several playlists with alternate
49260          // audio tracks and no audio present in the main TS segments.)
49261          this.emittedTracks++;
49262
49263          if (this.emittedTracks >= this.numberOfTracks) {
49264            this.trigger('done');
49265            this.emittedTracks = 0;
49266          }
49267
49268          return;
49269        }
49270      }
49271
49272      if (this.videoTrack) {
49273        timelineStartPts = this.videoTrack.timelineStartInfo.pts;
49274        videoProperties.forEach(function (prop) {
49275          event.info[prop] = this.videoTrack[prop];
49276        }, this);
49277      } else if (this.audioTrack) {
49278        timelineStartPts = this.audioTrack.timelineStartInfo.pts;
49279        audioProperties.forEach(function (prop) {
49280          event.info[prop] = this.audioTrack[prop];
49281        }, this);
49282      }
49283
49284      if (this.videoTrack || this.audioTrack) {
49285        if (this.pendingTracks.length === 1) {
49286          event.type = this.pendingTracks[0].type;
49287        } else {
49288          event.type = 'combined';
49289        }
49290
49291        this.emittedTracks += this.pendingTracks.length;
49292        initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
49293
49294        event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
49295        // and track definitions
49296
49297        event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
49298
49299        event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
49300
49301        for (i = 0; i < this.pendingBoxes.length; i++) {
49302          event.data.set(this.pendingBoxes[i], offset);
49303          offset += this.pendingBoxes[i].byteLength;
49304        } // Translate caption PTS times into second offsets to match the
49305        // video timeline for the segment, and add track info
49306
49307
49308        for (i = 0; i < this.pendingCaptions.length; i++) {
49309          caption = this.pendingCaptions[i];
49310          caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
49311          caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
49312          event.captionStreams[caption.stream] = true;
49313          event.captions.push(caption);
49314        } // Translate ID3 frame PTS times into second offsets to match the
49315        // video timeline for the segment
49316
49317
49318        for (i = 0; i < this.pendingMetadata.length; i++) {
49319          id3 = this.pendingMetadata[i];
49320          id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
49321          event.metadata.push(id3);
49322        } // We add this to every single emitted segment even though we only need
49323        // it for the first
49324
49325
49326        event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
49327
49328        this.pendingTracks.length = 0;
49329        this.videoTrack = null;
49330        this.pendingBoxes.length = 0;
49331        this.pendingCaptions.length = 0;
49332        this.pendingBytes = 0;
49333        this.pendingMetadata.length = 0; // Emit the built segment
49334        // We include captions and ID3 tags for backwards compatibility,
49335        // ideally we should send only video and audio in the data event
49336
49337        this.trigger('data', event); // Emit each caption to the outside world
49338        // Ideally, this would happen immediately on parsing captions,
49339        // but we need to ensure that video data is sent back first
vendor: 18,819 bytes, lines 49340-49814
49340        // so that caption timing can be adjusted to match video timing
49341
49342        for (i = 0; i < event.captions.length; i++) {
49343          caption = event.captions[i];
49344          this.trigger('caption', caption);
49345        } // Emit each id3 tag to the outside world
49346        // Ideally, this would happen immediately on parsing the tag,
49347        // but we need to ensure that video data is sent back first
49348        // so that ID3 frame timing can be adjusted to match video timing
49349
49350
49351        for (i = 0; i < event.metadata.length; i++) {
49352          id3 = event.metadata[i];
49353          this.trigger('id3Frame', id3);
49354        }
49355      } // Only emit `done` if all tracks have been flushed and emitted
49356
49357
49358      if (this.emittedTracks >= this.numberOfTracks) {
49359        this.trigger('done');
49360        this.emittedTracks = 0;
49361      }
49362    };
49363
49364    _CoalesceStream.prototype.setRemux = function (val) {
49365      this.remuxTracks = val;
49366    };
49367    /**
49368     * A Stream that expects MP2T binary data as input and produces
49369     * corresponding media segments, suitable for use with Media Source
49370     * Extension (MSE) implementations that support the ISO BMFF byte
49371     * stream format, like Chrome.
49372     */
49373
49374
49375    _Transmuxer = function Transmuxer(options) {
49376      var self = this,
49377          hasFlushed = true,
49378          videoTrack,
49379          audioTrack;
49380
49381      _Transmuxer.prototype.init.call(this);
49382
49383      options = options || {};
49384      this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
49385      this.transmuxPipeline_ = {};
49386
49387      this.setupAacPipeline = function () {
49388        var pipeline = {};
49389        this.transmuxPipeline_ = pipeline;
49390        pipeline.type = 'aac';
49391        pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
49392
49393        pipeline.aacStream = new aac();
49394        pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
49395        pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
49396        pipeline.adtsStream = new adts();
49397        pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
49398        pipeline.headOfPipeline = pipeline.aacStream;
49399        pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
49400        pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
49401        pipeline.metadataStream.on('timestamp', function (frame) {
49402          pipeline.aacStream.setTimestamp(frame.timeStamp);
49403        });
49404        pipeline.aacStream.on('data', function (data) {
49405          if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
49406            return;
49407          }
49408
49409          audioTrack = audioTrack || {
49410            timelineStartInfo: {
49411              baseMediaDecodeTime: self.baseMediaDecodeTime
49412            },
49413            codec: 'adts',
49414            type: 'audio'
49415          }; // hook up the audio segment stream to the first track with aac data
49416
49417          pipeline.coalesceStream.numberOfTracks++;
49418          pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
49419          pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49420          pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
49421
49422          pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
49423
49424          self.trigger('trackinfo', {
49425            hasAudio: !!audioTrack,
49426            hasVideo: !!videoTrack
49427          });
49428        }); // Re-emit any data coming from the coalesce stream to the outside world
49429
49430        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
49431
49432        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49433      };
49434
49435      this.setupTsPipeline = function () {
49436        var pipeline = {};
49437        this.transmuxPipeline_ = pipeline;
49438        pipeline.type = 'ts';
49439        pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
49440
49441        pipeline.packetStream = new m2ts_1.TransportPacketStream();
49442        pipeline.parseStream = new m2ts_1.TransportParseStream();
49443        pipeline.elementaryStream = new m2ts_1.ElementaryStream();
49444        pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream();
49445        pipeline.adtsStream = new adts();
49446        pipeline.h264Stream = new H264Stream();
49447        pipeline.captionStream = new m2ts_1.CaptionStream(options);
49448        pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
49449        pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
49450
49451        pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
49452        // demux the streams
49453
49454        pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
49455        pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
49456        pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
49457
49458        pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
49459        pipeline.elementaryStream.on('data', function (data) {
49460          var i;
49461
49462          if (data.type === 'metadata') {
49463            i = data.tracks.length; // scan the tracks listed in the metadata
49464
49465            while (i--) {
49466              if (!videoTrack && data.tracks[i].type === 'video') {
49467                videoTrack = data.tracks[i];
49468                videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49469              } else if (!audioTrack && data.tracks[i].type === 'audio') {
49470                audioTrack = data.tracks[i];
49471                audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49472              }
49473            } // hook up the video segment stream to the first track with h264 data
49474
49475
49476            if (videoTrack && !pipeline.videoSegmentStream) {
49477              pipeline.coalesceStream.numberOfTracks++;
49478              pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
49479              pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
49480              pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
49481                // When video emits timelineStartInfo data after a flush, we forward that
49482                // info to the AudioSegmentStream, if it exists, because video timeline
49483                // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
49484                // because this is a particularly subtle form of timestamp alteration.
49485                if (audioTrack && !options.keepOriginalTimestamps) {
49486                  audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
49487                  // very earliest DTS we have seen in video because Chrome will
49488                  // interpret any video track with a baseMediaDecodeTime that is
49489                  // non-zero as a gap.
49490
49491                  pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
49492                }
49493              });
49494              pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
49495              pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
49496              pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
49497                if (audioTrack) {
49498                  pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
49499                }
49500              });
49501              pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
49502
49503              pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
49504            }
49505
49506            if (audioTrack && !pipeline.audioSegmentStream) {
49507              // hook up the audio segment stream to the first track with aac data
49508              pipeline.coalesceStream.numberOfTracks++;
49509              pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
49510              pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49511              pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
49512              pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
49513
49514              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
49515            } // emit pmt info
49516
49517
49518            self.trigger('trackinfo', {
49519              hasAudio: !!audioTrack,
49520              hasVideo: !!videoTrack
49521            });
49522          }
49523        }); // Re-emit any data coming from the coalesce stream to the outside world
49524
49525        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
49526        pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
49527          id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
49528          self.trigger('id3Frame', id3Frame);
49529        });
49530        pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
49531
49532        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49533      }; // hook up the segment streams once track metadata is delivered
49534
49535
49536      this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
49537        var pipeline = this.transmuxPipeline_;
49538
49539        if (!options.keepOriginalTimestamps) {
49540          this.baseMediaDecodeTime = baseMediaDecodeTime;
49541        }
49542
49543        if (audioTrack) {
49544          audioTrack.timelineStartInfo.dts = undefined;
49545          audioTrack.timelineStartInfo.pts = undefined;
49546          trackDecodeInfo.clearDtsInfo(audioTrack);
49547
49548          if (pipeline.audioTimestampRolloverStream) {
49549            pipeline.audioTimestampRolloverStream.discontinuity();
49550          }
49551        }
49552
49553        if (videoTrack) {
49554          if (pipeline.videoSegmentStream) {
49555            pipeline.videoSegmentStream.gopCache_ = [];
49556          }
49557
49558          videoTrack.timelineStartInfo.dts = undefined;
49559          videoTrack.timelineStartInfo.pts = undefined;
49560          trackDecodeInfo.clearDtsInfo(videoTrack);
49561          pipeline.captionStream.reset();
49562        }
49563
49564        if (pipeline.timestampRolloverStream) {
49565          pipeline.timestampRolloverStream.discontinuity();
49566        }
49567      };
49568
49569      this.setAudioAppendStart = function (timestamp) {
49570        if (audioTrack) {
49571          this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
49572        }
49573      };
49574
49575      this.setRemux = function (val) {
49576        var pipeline = this.transmuxPipeline_;
49577        options.remux = val;
49578
49579        if (pipeline && pipeline.coalesceStream) {
49580          pipeline.coalesceStream.setRemux(val);
49581        }
49582      };
49583
49584      this.alignGopsWith = function (gopsToAlignWith) {
49585        if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
49586          this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
49587        }
49588      };
49589
49590      this.getLogTrigger_ = function (key) {
49591        var self = this;
49592        return function (event) {
49593          event.stream = key;
49594          self.trigger('log', event);
49595        };
49596      }; // feed incoming data to the front of the parsing pipeline
49597
49598
49599      this.push = function (data) {
49600        if (hasFlushed) {
49601          var isAac = isLikelyAacData(data);
49602
49603          if (isAac && this.transmuxPipeline_.type !== 'aac') {
49604            this.setupAacPipeline();
49605          } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
49606            this.setupTsPipeline();
49607          }
49608
49609          if (this.transmuxPipeline_) {
49610            var keys = Object.keys(this.transmuxPipeline_);
49611
49612            for (var i = 0; i < keys.length; i++) {
49613              var key = keys[i]; // skip non-stream keys and headOfPipeline
49614              // which is just a duplicate
49615
49616              if (key === 'headOfPipeline' || !this.transmuxPipeline_[key].on) {
49617                continue;
49618              }
49619
49620              this.transmuxPipeline_[key].on('log', this.getLogTrigger_(key));
49621            }
49622          }
49623
49624          hasFlushed = false;
49625        }
49626
49627        this.transmuxPipeline_.headOfPipeline.push(data);
49628      }; // flush any buffered data
49629
49630
49631      this.flush = function () {
49632        hasFlushed = true; // Start at the top of the pipeline and flush all pending work
49633
49634        this.transmuxPipeline_.headOfPipeline.flush();
49635      };
49636
49637      this.endTimeline = function () {
49638        this.transmuxPipeline_.headOfPipeline.endTimeline();
49639      };
49640
49641      this.reset = function () {
49642        if (this.transmuxPipeline_.headOfPipeline) {
49643          this.transmuxPipeline_.headOfPipeline.reset();
49644        }
49645      }; // Caption data has to be reset when seeking outside buffered range
49646
49647
49648      this.resetCaptions = function () {
49649        if (this.transmuxPipeline_.captionStream) {
49650          this.transmuxPipeline_.captionStream.reset();
49651        }
49652      };
49653    };
49654
49655    _Transmuxer.prototype = new stream();
49656    var transmuxer = {
49657      Transmuxer: _Transmuxer,
49658      VideoSegmentStream: _VideoSegmentStream,
49659      AudioSegmentStream: _AudioSegmentStream,
49660      AUDIO_PROPERTIES: audioProperties,
49661      VIDEO_PROPERTIES: videoProperties,
49662      // exported for testing
49663      generateSegmentTimingInfo: generateSegmentTimingInfo
49664    };
49665    /**
49666     * mux.js
49667     *
49668     * Copyright (c) Brightcove
49669     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
49670     */
49671
49672    var toUnsigned$3 = function toUnsigned(value) {
49673      return value >>> 0;
49674    };
49675
49676    var toHexString$1 = function toHexString(value) {
49677      return ('00' + value.toString(16)).slice(-2);
49678    };
49679
49680    var bin = {
49681      toUnsigned: toUnsigned$3,
49682      toHexString: toHexString$1
49683    };
49684
49685    var parseType$1 = function parseType(buffer) {
49686      var result = '';
49687      result += String.fromCharCode(buffer[0]);
49688      result += String.fromCharCode(buffer[1]);
49689      result += String.fromCharCode(buffer[2]);
49690      result += String.fromCharCode(buffer[3]);
49691      return result;
49692    };
49693
49694    var parseType_1 = parseType$1;
49695    var toUnsigned$2 = bin.toUnsigned;
49696
49697    var findBox = function findBox(data, path) {
49698      var results = [],
49699          i,
49700          size,
49701          type,
49702          end,
49703          subresults;
49704
49705      if (!path.length) {
49706        // short-circuit the search for empty paths
49707        return null;
49708      }
49709
49710      for (i = 0; i < data.byteLength;) {
49711        size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
49712        type = parseType_1(data.subarray(i + 4, i + 8));
49713        end = size > 1 ? i + size : data.byteLength;
49714
49715        if (type === path[0]) {
49716          if (path.length === 1) {
49717            // this is the end of the path and we've found the box we were
49718            // looking for
49719            results.push(data.subarray(i + 8, end));
49720          } else {
49721            // recursively search for the next box along the path
49722            subresults = findBox(data.subarray(i + 8, end), path.slice(1));
49723
49724            if (subresults.length) {
49725              results = results.concat(subresults);
49726            }
49727          }
49728        }
49729
49730        i = end;
49731      } // we've finished searching all of data
49732
49733
49734      return results;
49735    };
49736
49737    var findBox_1 = findBox;
49738    var toUnsigned$1 = bin.toUnsigned;
49739
49740    var tfdt = function tfdt(data) {
49741      var result = {
49742        version: data[0],
49743        flags: new Uint8Array(data.subarray(1, 4)),
49744        baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
49745      };
49746
49747      if (result.version === 1) {
49748        result.baseMediaDecodeTime *= Math.pow(2, 32);
49749        result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
49750      }
49751
49752      return result;
49753    };
49754
49755    var parseTfdt = tfdt;
49756
49757    var parseSampleFlags = function parseSampleFlags(flags) {
49758      return {
49759        isLeading: (flags[0] & 0x0c) >>> 2,
49760        dependsOn: flags[0] & 0x03,
49761        isDependedOn: (flags[1] & 0xc0) >>> 6,
49762        hasRedundancy: (flags[1] & 0x30) >>> 4,
49763        paddingValue: (flags[1] & 0x0e) >>> 1,
49764        isNonSyncSample: flags[1] & 0x01,
49765        degradationPriority: flags[2] << 8 | flags[3]
49766      };
49767    };
49768
49769    var parseSampleFlags_1 = parseSampleFlags;
49770
49771    var trun = function trun(data) {
49772      var result = {
49773        version: data[0],
49774        flags: new Uint8Array(data.subarray(1, 4)),
49775        samples: []
49776      },
49777          view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49778          // Flag interpretation
49779      dataOffsetPresent = result.flags[2] & 0x01,
49780          // compare with 2nd byte of 0x1
49781      firstSampleFlagsPresent = result.flags[2] & 0x04,
49782          // compare with 2nd byte of 0x4
49783      sampleDurationPresent = result.flags[1] & 0x01,
49784          // compare with 2nd byte of 0x100
49785      sampleSizePresent = result.flags[1] & 0x02,
49786          // compare with 2nd byte of 0x200
49787      sampleFlagsPresent = result.flags[1] & 0x04,
49788          // compare with 2nd byte of 0x400
49789      sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
49790          // compare with 2nd byte of 0x800
49791      sampleCount = view.getUint32(4),
49792          offset = 8,
49793          sample;
49794
49795      if (dataOffsetPresent) {
49796        // 32 bit signed integer
49797        result.dataOffset = view.getInt32(offset);
49798        offset += 4;
49799      } // Overrides the flags for the first sample only. The order of
49800      // optional values will be: duration, size, compositionTimeOffset
49801
49802
49803      if (firstSampleFlagsPresent && sampleCount) {
49804        sample = {
49805          flags: parseSampleFlags_1(data.subarray(offset, offset + 4))
49806        };
49807        offset += 4;
49808
49809        if (sampleDurationPresent) {
49810          sample.duration = view.getUint32(offset);
49811          offset += 4;
49812        }
49813
49814        if (sampleSizePresent) {
vendor: 4,340 bytes, lines 49815-49960
49815          sample.size = view.getUint32(offset);
49816          offset += 4;
49817        }
49818
49819        if (sampleCompositionTimeOffsetPresent) {
49820          if (result.version === 1) {
49821            sample.compositionTimeOffset = view.getInt32(offset);
49822          } else {
49823            sample.compositionTimeOffset = view.getUint32(offset);
49824          }
49825
49826          offset += 4;
49827        }
49828
49829        result.samples.push(sample);
49830        sampleCount--;
49831      }
49832
49833      while (sampleCount--) {
49834        sample = {};
49835
49836        if (sampleDurationPresent) {
49837          sample.duration = view.getUint32(offset);
49838          offset += 4;
49839        }
49840
49841        if (sampleSizePresent) {
49842          sample.size = view.getUint32(offset);
49843          offset += 4;
49844        }
49845
49846        if (sampleFlagsPresent) {
49847          sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4));
49848          offset += 4;
49849        }
49850
49851        if (sampleCompositionTimeOffsetPresent) {
49852          if (result.version === 1) {
49853            sample.compositionTimeOffset = view.getInt32(offset);
49854          } else {
49855            sample.compositionTimeOffset = view.getUint32(offset);
49856          }
49857
49858          offset += 4;
49859        }
49860
49861        result.samples.push(sample);
49862      }
49863
49864      return result;
49865    };
49866
49867    var parseTrun = trun;
49868
49869    var tfhd = function tfhd(data) {
49870      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49871          result = {
49872        version: data[0],
49873        flags: new Uint8Array(data.subarray(1, 4)),
49874        trackId: view.getUint32(4)
49875      },
49876          baseDataOffsetPresent = result.flags[2] & 0x01,
49877          sampleDescriptionIndexPresent = result.flags[2] & 0x02,
49878          defaultSampleDurationPresent = result.flags[2] & 0x08,
49879          defaultSampleSizePresent = result.flags[2] & 0x10,
49880          defaultSampleFlagsPresent = result.flags[2] & 0x20,
49881          durationIsEmpty = result.flags[0] & 0x010000,
49882          defaultBaseIsMoof = result.flags[0] & 0x020000,
49883          i;
49884      i = 8;
49885
49886      if (baseDataOffsetPresent) {
49887        i += 4; // truncate top 4 bytes
49888        // FIXME: should we read the full 64 bits?
49889
49890        result.baseDataOffset = view.getUint32(12);
49891        i += 4;
49892      }
49893
49894      if (sampleDescriptionIndexPresent) {
49895        result.sampleDescriptionIndex = view.getUint32(i);
49896        i += 4;
49897      }
49898
49899      if (defaultSampleDurationPresent) {
49900        result.defaultSampleDuration = view.getUint32(i);
49901        i += 4;
49902      }
49903
49904      if (defaultSampleSizePresent) {
49905        result.defaultSampleSize = view.getUint32(i);
49906        i += 4;
49907      }
49908
49909      if (defaultSampleFlagsPresent) {
49910        result.defaultSampleFlags = view.getUint32(i);
49911      }
49912
49913      if (durationIsEmpty) {
49914        result.durationIsEmpty = true;
49915      }
49916
49917      if (!baseDataOffsetPresent && defaultBaseIsMoof) {
49918        result.baseDataOffsetIsMoof = true;
49919      }
49920
49921      return result;
49922    };
49923
49924    var parseTfhd = tfhd;
49925    var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
49926    var CaptionStream = captionStream.CaptionStream;
49927    /**
49928      * Maps an offset in the mdat to a sample based on the the size of the samples.
49929      * Assumes that `parseSamples` has been called first.
49930      *
49931      * @param {Number} offset - The offset into the mdat
49932      * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
49933      * @return {?Object} The matching sample, or null if no match was found.
49934      *
49935      * @see ISO-BMFF-12/2015, Section 8.8.8
49936     **/
49937
49938    var mapToSample = function mapToSample(offset, samples) {
49939      var approximateOffset = offset;
49940
49941      for (var i = 0; i < samples.length; i++) {
49942        var sample = samples[i];
49943
49944        if (approximateOffset < sample.size) {
49945          return sample;
49946        }
49947
49948        approximateOffset -= sample.size;
49949      }
49950
49951      return null;
49952    };
49953    /**
49954      * Finds SEI nal units contained in a Media Data Box.
49955      * Assumes that `parseSamples` has been called first.
49956      *
49957      * @param {Uint8Array} avcStream - The bytes of the mdat
49958      * @param {Object[]} samples - The samples parsed out by `parseSamples`
49959      * @param {Number} trackId - The trackId of this video track
49960      * @return {Object[]}
49960 seiNals - the parsed SEI NALUs found.
49961      *   The contents of the seiNal should match what is expected by
49962      *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
49963      *
49964      * @see ISO-BMFF-12/2015, Section 8.1.1
49965      * @see Rec. ITU-T H.264, 7.3.2.3.1
49966     **/
49967
49968
49969    var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
49970      var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
49971          result = {
49972        logs: [],
49973        seiNals: []
49974      },
49975          seiNal,
49976          i,
49977          length,
49978          lastMatchedSample;
49979
49980      for (i = 0; i + 4 < avcStream.length; i += length) {
49981        length = avcView.getUint32(i);
49982        i += 4; // Bail if this doesn't appear to be an H264 stream
49983
49984        if (length <= 0) {
49985          continue;
49986        }
49987
49988        switch (avcStream[i] & 0x1F) {
49989          case 0x06:
49990            var data = avcStream.subarray(i + 1, i + 1 + length);
49991            var matchingSample = mapToSample(i, samples);
49992            seiNal = {
49993              nalUnitType: 'sei_rbsp',
49994              size: length,
49995              data: data,
49996              escapedRBSP: discardEmulationPreventionBytes(data),
49997              trackId: trackId
49998            };
49999
50000            if (matchingSample) {
50001              seiNal.pts = matchingSample.pts;
50002              seiNal.dts = matchingSample.dts;
50003              lastMatchedSample = matchingSample;
50004            } else if (lastMatchedSample) {
50005              // If a matching sample cannot be found, use the last
50006              // sample's values as they should be as close as possible
50007              seiNal.pts = lastMatchedSample.pts;
50008              seiNal.dts = lastMatchedSample.dts;
50009            } else {
50010              result.logs.push({
50011                level: 'warn',
50012                message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
50013              });
50014              break;
50015            }
50016
50017            result.seiNals.push(seiNal);
50018            break;
50019        }
50020      }
50021
50022      return result;
50023    };
50024    /**
50025      * Parses sample information out of Track Run Boxes and calculates
50026      * the absolute presentation and decode timestamps of each sample.
50027      *
50028      * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
50029      * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
50030          @see ISO-BMFF-12/2015, Section 8.8.12
50031      * @param {Object} tfhd - The parsed Track Fragment Header
50032      *   @see inspect.parseTfhd
50033      * @return {Object[]} the parsed samples
50034      *
50035      * @see ISO-BMFF-12/2015, Section 8.8.8
50036     **/
50037
50038
50039    var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
50040      var currentDts = baseMediaDecodeTime;
50041      var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
50042      var defaultSampleSize = tfhd.defaultSampleSize || 0;
50043      var trackId = tfhd.trackId;
50044      var allSamples = [];
50045      truns.forEach(function (trun) {
50046        // Note: We currently do not parse the sample table as well
50047        // as the trun. It's possible some sources will require this.
50048        // moov > trak > mdia > minf > stbl
50049        var trackRun = parseTrun(trun);
50050        var samples = trackRun.samples;
50051        samples.forEach(function (sample) {
50052          if (sample.duration === undefined) {
50053            sample.duration = defaultSampleDuration;
50054          }
50055
50056          if (sample.size === undefined) {
50057            sample.size = defaultSampleSize;
50058          }
50059
50060          sample.trackId = trackId;
50061          sample.dts = currentDts;
50062
50063          if (sample.compositionTimeOffset === undefined) {
50064            sample.compositionTimeOffset = 0;
50065          }
50066
50067          sample.pts = currentDts + sample.compositionTimeOffset;
50068          currentDts += sample.duration;
50069        });
50070        allSamples = allSamples.concat(samples);
50071      });
50072      return allSamples;
50073    };
50074    /**
50075      * Parses out caption nals from an FMP4 segment's video tracks.
50076      *
50077      * @param {Uint8Array} segment - The bytes of a single segment
50078      * @param {Number} videoTrackId - The trackId of a video track in the segment
50079      * @return {Object.<Number, Object[]>} A mapping of video trackId to
50080      *   a list of seiNals found in that track
50081     **/
50082
50083
50084    var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
50085      // To get the samples
50086      var trafs = findBox_1(segment, ['moof', 'traf']); // To get SEI NAL units
50087
50088      var mdats = findBox_1(segment, ['mdat']);
50089      var captionNals = {};
50090      var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
50091
vendor: 4,161 bytes, lines 50092-50200
50092      mdats.forEach(function (mdat, index) {
50093        var matchingTraf = trafs[index];
50094        mdatTrafPairs.push({
50095          mdat: mdat,
50096          traf: matchingTraf
50097        });
50098      });
50099      mdatTrafPairs.forEach(function (pair) {
50100        var mdat = pair.mdat;
50101        var traf = pair.traf;
50102        var tfhd = findBox_1(traf, ['tfhd']); // Exactly 1 tfhd per traf
50103
50104        var headerInfo = parseTfhd(tfhd[0]);
50105        var trackId = headerInfo.trackId;
50106        var tfdt = findBox_1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
50107
50108        var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
50109        var truns = findBox_1(traf, ['trun']);
50110        var samples;
50111        var result; // Only parse video data for the chosen video track
50112
50113        if (videoTrackId === trackId && truns.length > 0) {
50114          samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
50115          result = findSeiNals(mdat, samples, trackId);
50116
50117          if (!captionNals[trackId]) {
50118            captionNals[trackId] = {
50119              seiNals: [],
50120              logs: []
50121            };
50122          }
50123
50124          captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
50125          captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
50126        }
50127      });
50128      return captionNals;
50129    };
50130    /**
50131      * Parses out inband captions from an MP4 container and returns
50132      * caption objects that can be used by WebVTT and the TextTrack API.
50133      * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
50134      * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
50135      * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
50136      *
50137      * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
50138      * @param {Number} trackId - The id of the video track to parse
50139      * @param {Number} timescale - The timescale for the video track from the init segment
50140      *
50141      * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
50142      * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
50143      * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
50144      * @return {String} parsedCaptions[].text - The visible content of the caption
50145     **/
50146
50147
50148    var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
50149      var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
50150
50151      if (trackId === null) {
50152        return null;
50153      }
50154
50155      captionNals = parseCaptionNals(segment, trackId);
50156      var trackNals = captionNals[trackId] || {};
50157      return {
50158        seiNals: trackNals.seiNals,
50159        logs: trackNals.logs,
50160        timescale: timescale
50161      };
50162    };
50163    /**
50164      * Converts SEI NALUs into captions that can be used by video.js
50165     **/
50166
50167
50168    var CaptionParser = function CaptionParser() {
50169      var isInitialized = false;
50170      var captionStream; // Stores segments seen before trackId and timescale are set
50171
50172      var segmentCache; // Stores video track ID of the track being parsed
50173
50174      var trackId; // Stores the timescale of the track being parsed
50175
50176      var timescale; // Stores captions parsed so far
50177
50178      var parsedCaptions; // Stores whether we are receiving partial data or not
50179
50180      var parsingPartial;
50181      /**
50182        * A method to indicate whether a CaptionParser has been initalized
50183        * @returns {Boolean}
50184       **/
50185
50186      this.isInitialized = function () {
50187        return isInitialized;
50188      };
50189      /**
50190        * Initializes the underlying CaptionStream, SEI NAL parsing
50191        * and management, and caption collection
50192       **/
50193
50194
50195      this.init = function (options) {
50196        captionStream = new CaptionStream();
50197        isInitialized = true;
50198        parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
50199
50200        captionStream.on('data', function (event) {
vendor: 4,120 bytes, lines 50201-50316
50201          // Convert to seconds in the source's timescale
50202          event.startTime = event.startPts / timescale;
50203          event.endTime = event.endPts / timescale;
50204          parsedCaptions.captions.push(event);
50205          parsedCaptions.captionStreams[event.stream] = true;
50206        });
50207        captionStream.on('log', function (log) {
50208          parsedCaptions.logs.push(log);
50209        });
50210      };
50211      /**
50212        * Determines if a new video track will be selected
50213        * or if the timescale changed
50214        * @return {Boolean}
50215       **/
50216
50217
50218      this.isNewInit = function (videoTrackIds, timescales) {
50219        if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
50220          return false;
50221        }
50222
50223        return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
50224      };
50225      /**
50226        * Parses out SEI captions and interacts with underlying
50227        * CaptionStream to return dispatched captions
50228        *
50229        * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
50230        * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
50231        * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
50232        * @see parseEmbeddedCaptions
50233        * @see m2ts/caption-stream.js
50234       **/
50235
50236
50237      this.parse = function (segment, videoTrackIds, timescales) {
50238        var parsedData;
50239
50240        if (!this.isInitialized()) {
50241          return null; // This is not likely to be a video segment
50242        } else if (!videoTrackIds || !timescales) {
50243          return null;
50244        } else if (this.isNewInit(videoTrackIds, timescales)) {
50245          // Use the first video track only as there is no
50246          // mechanism to switch to other video tracks
50247          trackId = videoTrackIds[0];
50248          timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
50249          // data until we have one.
50250          // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
50251        } else if (trackId === null || !timescale) {
50252          segmentCache.push(segment);
50253          return null;
50254        } // Now that a timescale and trackId is set, parse cached segments
50255
50256
50257        while (segmentCache.length > 0) {
50258          var cachedSegment = segmentCache.shift();
50259          this.parse(cachedSegment, videoTrackIds, timescales);
50260        }
50261
50262        parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
50263
50264        if (parsedData && parsedData.logs) {
50265          parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
50266        }
50267
50268        if (parsedData === null || !parsedData.seiNals) {
50269          if (parsedCaptions.logs.length) {
50270            return {
50271              logs: parsedCaptions.logs,
50272              captions: [],
50273              captionStreams: []
50274            };
50275          }
50276
50277          return null;
50278        }
50279
50280        this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
50281
50282        this.flushStream();
50283        return parsedCaptions;
50284      };
50285      /**
50286        * Pushes SEI NALUs onto CaptionStream
50287        * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
50288        * Assumes that `parseCaptionNals` has been called first
50289        * @see m2ts/caption-stream.js
50290        **/
50291
50292
50293      this.pushNals = function (nals) {
50294        if (!this.isInitialized() || !nals || nals.length === 0) {
50295          return null;
50296        }
50297
50298        nals.forEach(function (nal) {
50299          captionStream.push(nal);
50300        });
50301      };
50302      /**
50303        * Flushes underlying CaptionStream to dispatch processed, displayable captions
50304        * @see m2ts/caption-stream.js
50305       **/
50306
50307
50308      this.flushStream = function () {
50309        if (!this.isInitialized()) {
50310          return null;
50311        }
50312
50313        if (!parsingPartial) {
50314          captionStream.flush();
50315        } else {
50316          captionStream.partialFlush();
50317        }
50318      };
50319      /**
50320        * Reset caption buckets for new data
50321       **/
50322
50323
50324      this.clearParsedCaptions = function () {
50325        parsedCaptions.captions = [];
50326        parsedCaptions.captionStreams = {};
50327        parsedCaptions.logs = [];
50328      };
50329      /**
50330        * Resets underlying CaptionStream
50331        * @see m2ts/caption-stream.js
50332       **/
50333
50334
50335      this.resetCaptionStream = function () {
50336        if (!this.isInitialized()) {
50337          return null;
50338        }
50339
50340        captionStream.reset();
50341      };
50342      /**
50343        * Convenience method to clear all captions flushed from the
50344        * CaptionStream and still being parsed
50345        * @see m2ts/caption-stream.js
50346       **/
50347
50348
50349      this.clearAllCaptions = function () {
50350        this.clearParsedCaptions();
50351        this.resetCaptionStream();
50352      };
50353      /**
50354        * Reset caption parser
50355       **/
50356
50357
50358      this.reset = function () {
50359        segmentCache = [];
50360        trackId = null;
50361        timescale = null;
50362
50363        if (!parsedCaptions) {
50364          parsedCaptions = {
50365            captions: [],
50366            // CC1, CC2, CC3, CC4
50367            captionStreams: {},
50368            logs: []
50369          };
50370        } else {
50371          this.clearParsedCaptions();
50372        }
50373
50374        this.resetCaptionStream();
50375      };
50376
50377      this.reset();
50378    };
50379
50380    var captionParser = CaptionParser;
50381    var toUnsigned = bin.toUnsigned;
50382    var toHexString = bin.toHexString;
50383    var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader;
50384    /**
50385     * Parses an MP4 initialization segment and extracts the timescale
50386     * values for any declared tracks. Timescale values indicate the
50387     * number of clock ticks per second to assume for time-based values
50388     * elsewhere in the MP4.
50389     *
50390     * To determine the start time of an MP4, you need two pieces of
50391     * information: the timescale unit and the earliest base media decode
50392     * time. Multiple timescales can be specified within an MP4 but the
50393     * base media decode time is always expressed in the timescale from
50394     * the media header box for the track:
50395     * ```
50396     * moov > trak > mdia > mdhd.timescale
50397     * ```
50398     * @param init {Uint8Array} the bytes of the init segment
50399     * @return {object} a hash of track ids to timescale values or null if
50400     * the init segment is malformed.
50401     */
50402
50403    timescale = function timescale(init) {
50404      var result = {},
50405          traks = findBox_1(init, ['moov', 'trak']); // mdhd timescale
50406
50407      return traks.reduce(function (result, trak) {
50408        var tkhd, version, index, id, mdhd;
50409        tkhd = findBox_1(trak, ['tkhd'])[0];
50410
50411        if (!tkhd) {
50412          return null;
50413        }
50414
50415        version = tkhd[0];
50416        index = version === 0 ? 12 : 20;
50417        id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
50418        mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
50419
50420        if (!mdhd) {
50421          return null;
50422        }
50423
50424        version = mdhd[0];
50425        index = version === 0 ? 12 : 20;
50426        result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50427        return result;
50428      }, result);
50429    };
50430    /**
50431     * Determine the base media decode start time, in seconds, for an MP4
50432     * fragment. If multiple fragments are specified, the earliest time is
50433     * returned.
50434     *
50435     * The base media decode time can be parsed from track fragment
50436     * metadata:
50437     * ```
50438     * moof > traf > tfdt.baseMediaDecodeTime
50439     * ```
50440     * It requires the timescale value from the mdhd to interpret.
50441     *
50442     * @param timescale {object} a hash of track ids to timescale values.
50443     * @return {number} the earliest base media decode start time for the
50444     * fragment, in seconds
50445     */
50446
50447
50448    startTime = function startTime(timescale, fragment) {
50449      var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
50450
50451      trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track
50452
50453      baseTimes = [].concat.apply([], trafs.map(function (traf) {
50454        return findBox_1(traf, ['tfhd']).map(function (tfhd) {
50455          var id, scale, baseTime; // get the track id from the tfhd
50456
50457          id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
50458
50459          scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
50460
50461          baseTime = findBox_1(traf, ['tfdt']).map(function (tfdt) {
50462            var version, result;
50463            version = tfdt[0];
50464            result = toUnsigned(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
50465
50466            if (version === 1) {
50467              result *= Math.pow(2, 32);
50468              result += toUnsigned(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
50469            }
50470
50471            return result;
50472          })[0];
50473          baseTime = typeof baseTime === 'number' && !isNaN(baseTime) ? baseTime : Infinity; // convert base time to seconds
50474
50475          return baseTime / scale;
50476        });
50477      })); // return the minimum
50478
50479      result = Math.min.apply(null, baseTimes);
50480      return isFinite(result) ? result : 0;
50481    };
50482    /**
50483     * Determine the composition start, in seconds, for an MP4
50484     * fragment.
50485     *
50486     * The composition start time of a fragment can be calculated using the base
50487     * media decode time, composition time offset, and timescale, as follows:
50488     *
50489     * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
50490     *
50491     * All of the aforementioned information is contained within a media fragment's
50492     * `traf` box, except for timescale info, which comes from the initialization
50493     * segment, so a track id (also contained within a `traf`) is also necessary to
50494     * associate it with a timescale
50495     *
50496     *
50497     * @param timescales {object} - a hash of track ids to timescale values.
50498     * @param fragment {Unit8Array} - the bytes of a media segment
50499     * @return {number} the composition start time for the fragment, in seconds
50500     **/
50501
50502
50503    compositionStartTime = function compositionStartTime(timescales, fragment) {
50504      var trafBoxes = findBox_1(fragment, ['moof', 'traf']);
50505      var baseMediaDecodeTime = 0;
50506      var compositionTimeOffset = 0;
50507      var trackId;
50508
50509      if (trafBoxes && trafBoxes.length) {
50510        // The spec states that track run samples contained within a `traf` box are contiguous, but
50511        // it does not explicitly state whether the `traf` boxes themselves are contiguous.
50512        // We will assume that they are, so we only need the first to calculate start time.
50513        var tfhd = findBox_1(trafBoxes[0], ['tfhd'])[0];
50514        var trun = findBox_1(trafBoxes[0], ['trun'])[0];
50515        var tfdt = findBox_1(trafBoxes[0], ['tfdt'])[0];
50516
50517        if (tfhd) {
50518          var parsedTfhd = parseTfhd(tfhd);
50519          trackId = parsedTfhd.trackId;
50520        }
50521
50522        if (tfdt) {
50523          var parsedTfdt = parseTfdt(tfdt);
50524          baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
50525        }
50526
50527        if (trun) {
50528          var parsedTrun = parseTrun(trun);
50529
50530          if (parsedTrun.samples && parsedTrun.samples.length) {
50531            compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
50532          }
50533        }
50534      } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
50535      // specified.
50536
50537
50538      var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
50539
50540      return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
50541    };
50542    /**
50543      * Find the trackIds of the video tracks in this source.
50544      * Found by parsing the Handler Reference and Track Header Boxes:
50545      *   moov > trak > mdia > hdlr
50546      *   moov > trak > tkhd
50547      *
50548      * @param {Uint8Array} init - The bytes of the init segment for this source
50549      * @return {Number[]} A list of trackIds
50550      *
50551      * @see ISO-BMFF-12/2015, Section 8.4.3
50552     **/
50553
50554
50555    getVideoTrackIds = function getVideoTrackIds(init) {
50556      var traks = findBox_1(init, ['moov', 'trak']);
50557      var videoTrackIds = [];
50558      traks.forEach(function (trak) {
50559        var hdlrs = findBox_1(trak, ['mdia', 'hdlr']);
50560        var tkhds = findBox_1(trak, ['tkhd']);
vendor: 29,447 bytes, lines 50561-51417
50561        hdlrs.forEach(function (hdlr, index) {
50562          var handlerType = parseType_1(hdlr.subarray(8, 12));
50563          var tkhd = tkhds[index];
50564          var view;
50565          var version;
50566          var trackId;
50567
50568          if (handlerType === 'vide') {
50569            view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50570            version = view.getUint8(0);
50571            trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
50572            videoTrackIds.push(trackId);
50573          }
50574        });
50575      });
50576      return videoTrackIds;
50577    };
50578
50579    getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) {
50580      // mdhd is a FullBox, meaning it will have its own version as the first byte
50581      var version = mdhd[0];
50582      var index = version === 0 ? 12 : 20;
50583      return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50584    };
50585    /**
50586     * Get all the video, audio, and hint tracks from a non fragmented
50587     * mp4 segment
50588     */
50589
50590
50591    getTracks = function getTracks(init) {
50592      var traks = findBox_1(init, ['moov', 'trak']);
50593      var tracks = [];
50594      traks.forEach(function (trak) {
50595        var track = {};
50596        var tkhd = findBox_1(trak, ['tkhd'])[0];
50597        var view, tkhdVersion; // id
50598
50599        if (tkhd) {
50600          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50601          tkhdVersion = view.getUint8(0);
50602          track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
50603        }
50604
50605        var hdlr = findBox_1(trak, ['mdia', 'hdlr'])[0]; // type
50606
50607        if (hdlr) {
50608          var type = parseType_1(hdlr.subarray(8, 12));
50609
50610          if (type === 'vide') {
50611            track.type = 'video';
50612          } else if (type === 'soun') {
50613            track.type = 'audio';
50614          } else {
50615            track.type = type;
50616          }
50617        } // codec
50618
50619
50620        var stsd = findBox_1(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
50621
50622        if (stsd) {
50623          var sampleDescriptions = stsd.subarray(8); // gives the codec type string
50624
50625          track.codec = parseType_1(sampleDescriptions.subarray(4, 8));
50626          var codecBox = findBox_1(sampleDescriptions, [track.codec])[0];
50627          var codecConfig, codecConfigType;
50628
50629          if (codecBox) {
50630            // https://tools.ietf.org/html/rfc6381#section-3.3
50631            if (/^[a-z]vc[1-9]$/i.test(track.codec)) {
50632              // we don't need anything but the "config" parameter of the
50633              // avc1 codecBox
50634              codecConfig = codecBox.subarray(78);
50635              codecConfigType = parseType_1(codecConfig.subarray(4, 8));
50636
50637              if (codecConfigType === 'avcC' && codecConfig.length > 11) {
50638                track.codec += '.'; // left padded with zeroes for single digit hex
50639                // profile idc
50640
50641                track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
50642
50643                track.codec += toHexString(codecConfig[10]); // level idc
50644
50645                track.codec += toHexString(codecConfig[11]);
50646              } else {
50647                // TODO: show a warning that we couldn't parse the codec
50648                // and are using the default
50649                track.codec = 'avc1.4d400d';
50650              }
50651            } else if (/^mp4[a,v]$/i.test(track.codec)) {
50652              // we do not need anything but the streamDescriptor of the mp4a codecBox
50653              codecConfig = codecBox.subarray(28);
50654              codecConfigType = parseType_1(codecConfig.subarray(4, 8));
50655
50656              if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
50657                track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
50658
50659                track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
50660              } else {
50661                // TODO: show a warning that we couldn't parse the codec
50662                // and are using the default
50663                track.codec = 'mp4a.40.2';
50664              }
50665            } else {
50666              // flac, opus, etc
50667              track.codec = track.codec.toLowerCase();
50668            }
50669          }
50670        }
50671
50672        var mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
50673
50674        if (mdhd) {
50675          track.timescale = getTimescaleFromMediaHeader(mdhd);
50676        }
50677
50678        tracks.push(track);
50679      });
50680      return tracks;
50681    };
50682
50683    var probe$2 = {
50684      // export mp4 inspector's findBox and parseType for backwards compatibility
50685      findBox: findBox_1,
50686      parseType: parseType_1,
50687      timescale: timescale,
50688      startTime: startTime,
50689      compositionStartTime: compositionStartTime,
50690      videoTrackIds: getVideoTrackIds,
50691      tracks: getTracks,
50692      getTimescaleFromMediaHeader: getTimescaleFromMediaHeader
50693    };
50694
50695    var parsePid = function parsePid(packet) {
50696      var pid = packet[1] & 0x1f;
50697      pid <<= 8;
50698      pid |= packet[2];
50699      return pid;
50700    };
50701
50702    var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
50703      return !!(packet[1] & 0x40);
50704    };
50705
50706    var parseAdaptionField = function parseAdaptionField(packet) {
50707      var offset = 0; // if an adaption field is present, its length is specified by the
50708      // fifth byte of the TS packet header. The adaptation field is
50709      // used to add stuffing to PES packets that don't fill a complete
50710      // TS packet, and to specify some forms of timing and control data
50711      // that we do not currently use.
50712
50713      if ((packet[3] & 0x30) >>> 4 > 0x01) {
50714        offset += packet[4] + 1;
50715      }
50716
50717      return offset;
50718    };
50719
50720    var parseType = function parseType(packet, pmtPid) {
50721      var pid = parsePid(packet);
50722
50723      if (pid === 0) {
50724        return 'pat';
50725      } else if (pid === pmtPid) {
50726        return 'pmt';
50727      } else if (pmtPid) {
50728        return 'pes';
50729      }
50730
50731      return null;
50732    };
50733
50734    var parsePat = function parsePat(packet) {
50735      var pusi = parsePayloadUnitStartIndicator(packet);
50736      var offset = 4 + parseAdaptionField(packet);
50737
50738      if (pusi) {
50739        offset += packet[offset] + 1;
50740      }
50741
50742      return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
50743    };
50744
50745    var parsePmt = function parsePmt(packet) {
50746      var programMapTable = {};
50747      var pusi = parsePayloadUnitStartIndicator(packet);
50748      var payloadOffset = 4 + parseAdaptionField(packet);
50749
50750      if (pusi) {
50751        payloadOffset += packet[payloadOffset] + 1;
50752      } // PMTs can be sent ahead of the time when they should actually
50753      // take effect. We don't believe this should ever be the case
50754      // for HLS but we'll ignore "forward" PMT declarations if we see
50755      // them. Future PMT declarations have the current_next_indicator
50756      // set to zero.
50757
50758
50759      if (!(packet[payloadOffset + 5] & 0x01)) {
50760        return;
50761      }
50762
50763      var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
50764
50765      sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
50766      tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
50767      // long the program info descriptors are
50768
50769      programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
50770
50771      var offset = 12 + programInfoLength;
50772
50773      while (offset < tableEnd) {
50774        var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
50775
50776        programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
50777        // skip past the elementary stream descriptors, if present
50778
50779        offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
50780      }
50781
50782      return programMapTable;
50783    };
50784
50785    var parsePesType = function parsePesType(packet, programMapTable) {
50786      var pid = parsePid(packet);
50787      var type = programMapTable[pid];
50788
50789      switch (type) {
50790        case streamTypes.H264_STREAM_TYPE:
50791          return 'video';
50792
50793        case streamTypes.ADTS_STREAM_TYPE:
50794          return 'audio';
50795
50796        case streamTypes.METADATA_STREAM_TYPE:
50797          return 'timed-metadata';
50798
50799        default:
50800          return null;
50801      }
50802    };
50803
50804    var parsePesTime = function parsePesTime(packet) {
50805      var pusi = parsePayloadUnitStartIndicator(packet);
50806
50807      if (!pusi) {
50808        return null;
50809      }
50810
50811      var offset = 4 + parseAdaptionField(packet);
50812
50813      if (offset >= packet.byteLength) {
50814        // From the H 222.0 MPEG-TS spec
50815        // "For transport stream packets carrying PES packets, stuffing is needed when there
50816        //  is insufficient PES packet data to completely fill the transport stream packet
50817        //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
50818        //  the sum of the lengths of the data elements in it, so that the payload bytes
50819        //  remaining after the adaptation field exactly accommodates the available PES packet
50820        //  data."
50821        //
50822        // If the offset is >= the length of the packet, then the packet contains no data
50823        // and instead is just adaption field stuffing bytes
50824        return null;
50825      }
50826
50827      var pes = null;
50828      var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
50829      // and a DTS value. Determine what combination of values is
50830      // available to work with.
50831
50832      ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
50833      // performs all bitwise operations on 32-bit integers but javascript
50834      // supports a much greater range (52-bits) of integer using standard
50835      // mathematical operations.
50836      // We construct a 31-bit value using bitwise operators over the 31
50837      // most significant bits and then multiply by 4 (equal to a left-shift
50838      // of 2) before we add the final 2 least significant bits of the
50839      // timestamp (equal to an OR.)
50840
50841      if (ptsDtsFlags & 0xC0) {
50842        pes = {}; // the PTS and DTS are not written out directly. For information
50843        // on how they are encoded, see
50844        // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
50845
50846        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;
50847        pes.pts *= 4; // Left shift by 2
50848
50849        pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
50850
50851        pes.dts = pes.pts;
50852
50853        if (ptsDtsFlags & 0x40) {
50854          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;
50855          pes.dts *= 4; // Left shift by 2
50856
50857          pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
50858        }
50859      }
50860
50861      return pes;
50862    };
50863
50864    var parseNalUnitType = function parseNalUnitType(type) {
50865      switch (type) {
50866        case 0x05:
50867          return 'slice_layer_without_partitioning_rbsp_idr';
50868
50869        case 0x06:
50870          return 'sei_rbsp';
50871
50872        case 0x07:
50873          return 'seq_parameter_set_rbsp';
50874
50875        case 0x08:
50876          return 'pic_parameter_set_rbsp';
50877
50878        case 0x09:
50879          return 'access_unit_delimiter_rbsp';
50880
50881        default:
50882          return null;
50883      }
50884    };
50885
50886    var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
50887      var offset = 4 + parseAdaptionField(packet);
50888      var frameBuffer = packet.subarray(offset);
50889      var frameI = 0;
50890      var frameSyncPoint = 0;
50891      var foundKeyFrame = false;
50892      var nalType; // advance the sync point to a NAL start, if necessary
50893
50894      for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
50895        if (frameBuffer[frameSyncPoint + 2] === 1) {
50896          // the sync point is properly aligned
50897          frameI = frameSyncPoint + 5;
50898          break;
50899        }
50900      }
50901
50902      while (frameI < frameBuffer.byteLength) {
50903        // look at the current byte to determine if we've hit the end of
50904        // a NAL unit boundary
50905        switch (frameBuffer[frameI]) {
50906          case 0:
50907            // skip past non-sync sequences
50908            if (frameBuffer[frameI - 1] !== 0) {
50909              frameI += 2;
50910              break;
50911            } else if (frameBuffer[frameI - 2] !== 0) {
50912              frameI++;
50913              break;
50914            }
50915
50916            if (frameSyncPoint + 3 !== frameI - 2) {
50917              nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50918
50919              if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50920                foundKeyFrame = true;
50921              }
50922            } // drop trailing zeroes
50923
50924
50925            do {
50926              frameI++;
50927            } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
50928
50929            frameSyncPoint = frameI - 2;
50930            frameI += 3;
50931            break;
50932
50933          case 1:
50934            // skip past non-sync sequences
50935            if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
50936              frameI += 3;
50937              break;
50938            }
50939
50940            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50941
50942            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50943              foundKeyFrame = true;
50944            }
50945
50946            frameSyncPoint = frameI - 2;
50947            frameI += 3;
50948            break;
50949
50950          default:
50951            // the current byte isn't a one or zero, so it cannot be part
50952            // of a sync sequence
50953            frameI += 3;
50954            break;
50955        }
50956      }
50957
50958      frameBuffer = frameBuffer.subarray(frameSyncPoint);
50959      frameI -= frameSyncPoint;
50960      frameSyncPoint = 0; // parse the final nal
50961
50962      if (frameBuffer && frameBuffer.byteLength > 3) {
50963        nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50964
50965        if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50966          foundKeyFrame = true;
50967        }
50968      }
50969
50970      return foundKeyFrame;
50971    };
50972
50973    var probe$1 = {
50974      parseType: parseType,
50975      parsePat: parsePat,
50976      parsePmt: parsePmt,
50977      parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
50978      parsePesType: parsePesType,
50979      parsePesTime: parsePesTime,
50980      videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
50981    };
50982    var handleRollover = timestampRolloverStream.handleRollover;
50983    var probe = {};
50984    probe.ts = probe$1;
50985    probe.aac = utils;
50986    var ONE_SECOND_IN_TS = clock.ONE_SECOND_IN_TS;
50987    var MP2T_PACKET_LENGTH = 188,
50988        // bytes
50989    SYNC_BYTE = 0x47;
50990    /**
50991     * walks through segment data looking for pat and pmt packets to parse out
50992     * program map table information
50993     */
50994
50995    var parsePsi_ = function parsePsi_(bytes, pmt) {
50996      var startIndex = 0,
50997          endIndex = MP2T_PACKET_LENGTH,
50998          packet,
50999          type;
51000
51001      while (endIndex < bytes.byteLength) {
51002        // Look for a pair of start and end sync bytes in the data..
51003        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51004          // We found a packet
51005          packet = bytes.subarray(startIndex, endIndex);
51006          type = probe.ts.parseType(packet, pmt.pid);
51007
51008          switch (type) {
51009            case 'pat':
51010              pmt.pid = probe.ts.parsePat(packet);
51011              break;
51012
51013            case 'pmt':
51014              var table = probe.ts.parsePmt(packet);
51015              pmt.table = pmt.table || {};
51016              Object.keys(table).forEach(function (key) {
51017                pmt.table[key] = table[key];
51018              });
51019              break;
51020          }
51021
51022          startIndex += MP2T_PACKET_LENGTH;
51023          endIndex += MP2T_PACKET_LENGTH;
51024          continue;
51025        } // If we get here, we have somehow become de-synchronized and we need to step
51026        // forward one byte at a time until we find a pair of sync bytes that denote
51027        // a packet
51028
51029
51030        startIndex++;
51031        endIndex++;
51032      }
51033    };
51034    /**
51035     * walks through the segment data from the start and end to get timing information
51036     * for the first and last audio pes packets
51037     */
51038
51039
51040    var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
51041      var startIndex = 0,
51042          endIndex = MP2T_PACKET_LENGTH,
51043          packet,
51044          type,
51045          pesType,
51046          pusi,
51047          parsed;
51048      var endLoop = false; // Start walking from start of segment to get first audio packet
51049
51050      while (endIndex <= bytes.byteLength) {
51051        // Look for a pair of start and end sync bytes in the data..
51052        if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
51053          // We found a packet
51054          packet = bytes.subarray(startIndex, endIndex);
51055          type = probe.ts.parseType(packet, pmt.pid);
51056
51057          switch (type) {
51058            case 'pes':
51059              pesType = probe.ts.parsePesType(packet, pmt.table);
51060              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51061
51062              if (pesType === 'audio' && pusi) {
51063                parsed = probe.ts.parsePesTime(packet);
51064
51065                if (parsed) {
51066                  parsed.type = 'audio';
51067                  result.audio.push(parsed);
51068                  endLoop = true;
51069                }
51070              }
51071
51072              break;
51073          }
51074
51075          if (endLoop) {
51076            break;
51077          }
51078
51079          startIndex += MP2T_PACKET_LENGTH;
51080          endIndex += MP2T_PACKET_LENGTH;
51081          continue;
51082        } // If we get here, we have somehow become de-synchronized and we need to step
51083        // forward one byte at a time until we find a pair of sync bytes that denote
51084        // a packet
51085
51086
51087        startIndex++;
51088        endIndex++;
51089      } // Start walking from end of segment to get last audio packet
51090
51091
51092      endIndex = bytes.byteLength;
51093      startIndex = endIndex - MP2T_PACKET_LENGTH;
51094      endLoop = false;
51095
51096      while (startIndex >= 0) {
51097        // Look for a pair of start and end sync bytes in the data..
51098        if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
51099          // We found a packet
51100          packet = bytes.subarray(startIndex, endIndex);
51101          type = probe.ts.parseType(packet, pmt.pid);
51102
51103          switch (type) {
51104            case 'pes':
51105              pesType = probe.ts.parsePesType(packet, pmt.table);
51106              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51107
51108              if (pesType === 'audio' && pusi) {
51109                parsed = probe.ts.parsePesTime(packet);
51110
51111                if (parsed) {
51112                  parsed.type = 'audio';
51113                  result.audio.push(parsed);
51114                  endLoop = true;
51115                }
51116              }
51117
51118              break;
51119          }
51120
51121          if (endLoop) {
51122            break;
51123          }
51124
51125          startIndex -= MP2T_PACKET_LENGTH;
51126          endIndex -= MP2T_PACKET_LENGTH;
51127          continue;
51128        } // If we get here, we have somehow become de-synchronized and we need to step
51129        // forward one byte at a time until we find a pair of sync bytes that denote
51130        // a packet
51131
51132
51133        startIndex--;
51134        endIndex--;
51135      }
51136    };
51137    /**
51138     * walks through the segment data from the start and end to get timing information
51139     * for the first and last video pes packets as well as timing information for the first
51140     * key frame.
51141     */
51142
51143
51144    var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
51145      var startIndex = 0,
51146          endIndex = MP2T_PACKET_LENGTH,
51147          packet,
51148          type,
51149          pesType,
51150          pusi,
51151          parsed,
51152          frame,
51153          i,
51154          pes;
51155      var endLoop = false;
51156      var currentFrame = {
51157        data: [],
51158        size: 0
51159      }; // Start walking from start of segment to get first video packet
51160
51161      while (endIndex < bytes.byteLength) {
51162        // Look for a pair of start and end sync bytes in the data..
51163        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51164          // We found a packet
51165          packet = bytes.subarray(startIndex, endIndex);
51166          type = probe.ts.parseType(packet, pmt.pid);
51167
51168          switch (type) {
51169            case 'pes':
51170              pesType = probe.ts.parsePesType(packet, pmt.table);
51171              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51172
51173              if (pesType === 'video') {
51174                if (pusi && !endLoop) {
51175                  parsed = probe.ts.parsePesTime(packet);
51176
51177                  if (parsed) {
51178                    parsed.type = 'video';
51179                    result.video.push(parsed);
51180                    endLoop = true;
51181                  }
51182                }
51183
51184                if (!result.firstKeyFrame) {
51185                  if (pusi) {
51186                    if (currentFrame.size !== 0) {
51187                      frame = new Uint8Array(currentFrame.size);
51188                      i = 0;
51189
51190                      while (currentFrame.data.length) {
51191                        pes = currentFrame.data.shift();
51192                        frame.set(pes, i);
51193                        i += pes.byteLength;
51194                      }
51195
51196                      if (probe.ts.videoPacketContainsKeyFrame(frame)) {
51197                        var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
51198                        // the keyframe seems to work fine with HLS playback
51199                        // and definitely preferable to a crash with TypeError...
51200
51201                        if (firstKeyFrame) {
51202                          result.firstKeyFrame = firstKeyFrame;
51203                          result.firstKeyFrame.type = 'video';
51204                        } else {
51205                          // eslint-disable-next-line
51206                          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.');
51207                        }
51208                      }
51209
51210                      currentFrame.size = 0;
51211                    }
51212                  }
51213
51214                  currentFrame.data.push(packet);
51215                  currentFrame.size += packet.byteLength;
51216                }
51217              }
51218
51219              break;
51220          }
51221
51222          if (endLoop && result.firstKeyFrame) {
51223            break;
51224          }
51225
51226          startIndex += MP2T_PACKET_LENGTH;
51227          endIndex += MP2T_PACKET_LENGTH;
51228          continue;
51229        } // If we get here, we have somehow become de-synchronized and we need to step
51230        // forward one byte at a time until we find a pair of sync bytes that denote
51231        // a packet
51232
51233
51234        startIndex++;
51235        endIndex++;
51236      } // Start walking from end of segment to get last video packet
51237
51238
51239      endIndex = bytes.byteLength;
51240      startIndex = endIndex - MP2T_PACKET_LENGTH;
51241      endLoop = false;
51242
51243      while (startIndex >= 0) {
51244        // Look for a pair of start and end sync bytes in the data..
51245        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51246          // We found a packet
51247          packet = bytes.subarray(startIndex, endIndex);
51248          type = probe.ts.parseType(packet, pmt.pid);
51249
51250          switch (type) {
51251            case 'pes':
51252              pesType = probe.ts.parsePesType(packet, pmt.table);
51253              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51254
51255              if (pesType === 'video' && pusi) {
51256                parsed = probe.ts.parsePesTime(packet);
51257
51258                if (parsed) {
51259                  parsed.type = 'video';
51260                  result.video.push(parsed);
51261                  endLoop = true;
51262                }
51263              }
51264
51265              break;
51266          }
51267
51268          if (endLoop) {
51269            break;
51270          }
51271
51272          startIndex -= MP2T_PACKET_LENGTH;
51273          endIndex -= MP2T_PACKET_LENGTH;
51274          continue;
51275        } // If we get here, we have somehow become de-synchronized and we need to step
51276        // forward one byte at a time until we find a pair of sync bytes that denote
51277        // a packet
51278
51279
51280        startIndex--;
51281        endIndex--;
51282      }
51283    };
51284    /**
51285     * Adjusts the timestamp information for the segment to account for
51286     * rollover and convert to seconds based on pes packet timescale (90khz clock)
51287     */
51288
51289
51290    var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
51291      if (segmentInfo.audio && segmentInfo.audio.length) {
51292        var audioBaseTimestamp = baseTimestamp;
51293
51294        if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
51295          audioBaseTimestamp = segmentInfo.audio[0].dts;
51296        }
51297
51298        segmentInfo.audio.forEach(function (info) {
51299          info.dts = handleRollover(info.dts, audioBaseTimestamp);
51300          info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
51301
51302          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51303          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51304        });
51305      }
51306
51307      if (segmentInfo.video && segmentInfo.video.length) {
51308        var videoBaseTimestamp = baseTimestamp;
51309
51310        if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
51311          videoBaseTimestamp = segmentInfo.video[0].dts;
51312        }
51313
51314        segmentInfo.video.forEach(function (info) {
51315          info.dts = handleRollover(info.dts, videoBaseTimestamp);
51316          info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
51317
51318          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51319          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51320        });
51321
51322        if (segmentInfo.firstKeyFrame) {
51323          var frame = segmentInfo.firstKeyFrame;
51324          frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
51325          frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
51326
51327          frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
51328          frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
51329        }
51330      }
51331    };
51332    /**
51333     * inspects the aac data stream for start and end time information
51334     */
51335
51336
51337    var inspectAac_ = function inspectAac_(bytes) {
51338      var endLoop = false,
51339          audioCount = 0,
51340          sampleRate = null,
51341          timestamp = null,
51342          frameSize = 0,
51343          byteIndex = 0,
51344          packet;
51345
51346      while (bytes.length - byteIndex >= 3) {
51347        var type = probe.aac.parseType(bytes, byteIndex);
51348
51349        switch (type) {
51350          case 'timed-metadata':
51351            // Exit early because we don't have enough to parse
51352            // the ID3 tag header
51353            if (bytes.length - byteIndex < 10) {
51354              endLoop = true;
51355              break;
51356            }
51357
51358            frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51359            // to emit a full packet
51360
51361            if (frameSize > bytes.length) {
51362              endLoop = true;
51363              break;
51364            }
51365
51366            if (timestamp === null) {
51367              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51368              timestamp = probe.aac.parseAacTimestamp(packet);
51369            }
51370
51371            byteIndex += frameSize;
51372            break;
51373
51374          case 'audio':
51375            // Exit early because we don't have enough to parse
51376            // the ADTS frame header
51377            if (bytes.length - byteIndex < 7) {
51378              endLoop = true;
51379              break;
51380            }
51381
51382            frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51383            // to emit a full packet
51384
51385            if (frameSize > bytes.length) {
51386              endLoop = true;
51387              break;
51388            }
51389
51390            if (sampleRate === null) {
51391              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51392              sampleRate = probe.aac.parseSampleRate(packet);
51393            }
51394
51395            audioCount++;
51396            byteIndex += frameSize;
51397            break;
51398
51399          default:
51400            byteIndex++;
51401            break;
51402        }
51403
51404        if (endLoop) {
51405          return null;
51406        }
51407      }
51408
51409      if (sampleRate === null || timestamp === null) {
51410        return null;
51411      }
51412
51413      var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
51414      var result = {
51415        audio: [{
51416          type: 'audio',
51417          dts: timestamp,
vendor: 7,740 bytes, lines 51418-51656
51418          pts: timestamp
51419        }, {
51420          type: 'audio',
51421          dts: timestamp + audioCount * 1024 * audioTimescale,
51422          pts: timestamp + audioCount * 1024 * audioTimescale
51423        }]
51424      };
51425      return result;
51426    };
51427    /**
51428     * inspects the transport stream segment data for start and end time information
51429     * of the audio and video tracks (when present) as well as the first key frame's
51430     * start time.
51431     */
51432
51433
51434    var inspectTs_ = function inspectTs_(bytes) {
51435      var pmt = {
51436        pid: null,
51437        table: null
51438      };
51439      var result = {};
51440      parsePsi_(bytes, pmt);
51441
51442      for (var pid in pmt.table) {
51443        if (pmt.table.hasOwnProperty(pid)) {
51444          var type = pmt.table[pid];
51445
51446          switch (type) {
51447            case streamTypes.H264_STREAM_TYPE:
51448              result.video = [];
51449              parseVideoPes_(bytes, pmt, result);
51450
51451              if (result.video.length === 0) {
51452                delete result.video;
51453              }
51454
51455              break;
51456
51457            case streamTypes.ADTS_STREAM_TYPE:
51458              result.audio = [];
51459              parseAudioPes_(bytes, pmt, result);
51460
51461              if (result.audio.length === 0) {
51462                delete result.audio;
51463              }
51464
51465              break;
51466          }
51467        }
51468      }
51469
51470      return result;
51471    };
51472    /**
51473     * Inspects segment byte data and returns an object with start and end timing information
51474     *
51475     * @param {Uint8Array} bytes The segment byte data
51476     * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
51477     *  timestamps for rollover. This value must be in 90khz clock.
51478     * @return {Object} Object containing start and end frame timing info of segment.
51479     */
51480
51481
51482    var inspect = function inspect(bytes, baseTimestamp) {
51483      var isAacData = probe.aac.isLikelyAacData(bytes);
51484      var result;
51485
51486      if (isAacData) {
51487        result = inspectAac_(bytes);
51488      } else {
51489        result = inspectTs_(bytes);
51490      }
51491
51492      if (!result || !result.audio && !result.video) {
51493        return null;
51494      }
51495
51496      adjustTimestamp_(result, baseTimestamp);
51497      return result;
51498    };
51499
51500    var tsInspector = {
51501      inspect: inspect,
51502      parseAudioPes_: parseAudioPes_
51503    };
51504    /* global self */
51505
51506    /**
51507     * Re-emits transmuxer events by converting them into messages to the
51508     * world outside the worker.
51509     *
51510     * @param {Object} transmuxer the transmuxer to wire events on
51511     * @private
51512     */
51513
51514    var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
51515      transmuxer.on('data', function (segment) {
51516        // transfer ownership of the underlying ArrayBuffer
51517        // instead of doing a copy to save memory
51518        // ArrayBuffers are transferable but generic TypedArrays are not
51519        // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
51520        var initArray = segment.initSegment;
51521        segment.initSegment = {
51522          data: initArray.buffer,
51523          byteOffset: initArray.byteOffset,
51524          byteLength: initArray.byteLength
51525        };
51526        var typedArray = segment.data;
51527        segment.data = typedArray.buffer;
51528        self.postMessage({
51529          action: 'data',
51530          segment: segment,
51531          byteOffset: typedArray.byteOffset,
51532          byteLength: typedArray.byteLength
51533        }, [segment.data]);
51534      });
51535      transmuxer.on('done', function (data) {
51536        self.postMessage({
51537          action: 'done'
51538        });
51539      });
51540      transmuxer.on('gopInfo', function (gopInfo) {
51541        self.postMessage({
51542          action: 'gopInfo',
51543          gopInfo: gopInfo
51544        });
51545      });
51546      transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
51547        var videoSegmentTimingInfo = {
51548          start: {
51549            decode: clock.videoTsToSeconds(timingInfo.start.dts),
51550            presentation: clock.videoTsToSeconds(timingInfo.start.pts)
51551          },
51552          end: {
51553            decode: clock.videoTsToSeconds(timingInfo.end.dts),
51554            presentation: clock.videoTsToSeconds(timingInfo.end.pts)
51555          },
51556          baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51557        };
51558
51559        if (timingInfo.prependedContentDuration) {
51560          videoSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration);
51561        }
51562
51563        self.postMessage({
51564          action: 'videoSegmentTimingInfo',
51565          videoSegmentTimingInfo: videoSegmentTimingInfo
51566        });
51567      });
51568      transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
51569        // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
51570        var audioSegmentTimingInfo = {
51571          start: {
51572            decode: clock.videoTsToSeconds(timingInfo.start.dts),
51573            presentation: clock.videoTsToSeconds(timingInfo.start.pts)
51574          },
51575          end: {
51576            decode: clock.videoTsToSeconds(timingInfo.end.dts),
51577            presentation: clock.videoTsToSeconds(timingInfo.end.pts)
51578          },
51579          baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51580        };
51581
51582        if (timingInfo.prependedContentDuration) {
51583          audioSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration);
51584        }
51585
51586        self.postMessage({
51587          action: 'audioSegmentTimingInfo',
51588          audioSegmentTimingInfo: audioSegmentTimingInfo
51589        });
51590      });
51591      transmuxer.on('id3Frame', function (id3Frame) {
51592        self.postMessage({
51593          action: 'id3Frame',
51594          id3Frame: id3Frame
51595        });
51596      });
51597      transmuxer.on('caption', function (caption) {
51598        self.postMessage({
51599          action: 'caption',
51600          caption: caption
51601        });
51602      });
51603      transmuxer.on('trackinfo', function (trackInfo) {
51604        self.postMessage({
51605          action: 'trackinfo',
51606          trackInfo: trackInfo
51607        });
51608      });
51609      transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
51610        // convert to video TS since we prioritize video time over audio
51611        self.postMessage({
51612          action: 'audioTimingInfo',
51613          audioTimingInfo: {
51614            start: clock.videoTsToSeconds(audioTimingInfo.start),
51615            end: clock.videoTsToSeconds(audioTimingInfo.end)
51616          }
51617        });
51618      });
51619      transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
51620        self.postMessage({
51621          action: 'videoTimingInfo',
51622          videoTimingInfo: {
51623            start: clock.videoTsToSeconds(videoTimingInfo.start),
51624            end: clock.videoTsToSeconds(videoTimingInfo.end)
51625          }
51626        });
51627      });
51628      transmuxer.on('log', function (log) {
51629        self.postMessage({
51630          action: 'log',
51631          log: log
51632        });
51633      });
51634    };
51635    /**
51636     * All incoming messages route through this hash. If no function exists
51637     * to handle an incoming message, then we ignore the message.
51638     *
51639     * @class MessageHandlers
51640     * @param {Object} options the options to initialize with
51641     */
51642
51643
51644    var MessageHandlers = /*#__PURE__*/function () {
51645      function MessageHandlers(self, options) {
51646        this.options = options || {};
51647        this.self = self;
51648        this.init();
51649      }
51650      /**
51651       * initialize our web worker and wire all the events.
51652       */
51653
51654
51655      var _proto = MessageHandlers.prototype;
51656
51657      _proto.init = function init() {
51658        if (this.transmuxer) {
51659          this.transmuxer.dispose();
51660        }
51661
51662        this.transmuxer = new transmuxer.Transmuxer(this.options);
51663        wireTransmuxerEvents(this.self, this.transmuxer);
51664      };
51665
51666      _proto.pushMp4Captions = function pushMp4Captions(data) {
51667        if (!this.captionParser) {
51668          this.captionParser = new captionParser();
51669          this.captionParser.init();
51670        }
51671
51672        var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51673        var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
51674        this.self.postMessage({
51675          action: 'mp4Captions',
51676          captions: parsed && parsed.captions || [],
51677          logs: parsed && parsed.logs || [],
51678          data: segment.buffer
51679        }, [segment.buffer]);
51680      };
51681
51682      _proto.probeMp4StartTime = function probeMp4StartTime(_ref) {
51683        var timescales = _ref.timescales,
51684            data = _ref.data;
51685        var startTime = probe$2.startTime(timescales, data);
51686        this.self.postMessage({
51687          action: 'probeMp4StartTime',
51688          startTime: startTime,
51689          data: data
51690        }, [data.buffer]);
51691      };
51692
51693      _proto.probeMp4Tracks = function probeMp4Tracks(_ref2) {
51694        var data = _ref2.data;
51695        var tracks = probe$2.tracks(data);
51696        this.self.postMessage({
51697          action: 'probeMp4Tracks',
51698          tracks: tracks,
51699          data: data
51700        }, [data.buffer]);
51701      }
51702      /**
51703       * Probe an mpeg2-ts segment to determine the start time of the segment in it's
51704       * internal "media time," as well as whether it contains video and/or audio.
51705       *
51706       * @private
51707       * @param {Uint8Array} bytes - segment bytes
51708       * @param {number} baseStartTime
51709       *        Relative reference timestamp used when adjusting frame timestamps for rollover.
51710       *        This value should be in seconds, as it's converted to a 90khz clock within the
51711       *        function body.
51712       * @return {Object} The start time of the current segment in "media time" as well as
51713       *                  whether it contains video and/or audio
51714       */
51715      ;
51716
51717      _proto.probeTs = function probeTs(_ref3) {
51718        var data = _ref3.data,
51719            baseStartTime = _ref3.baseStartTime;
51720        var tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock.ONE_SECOND_IN_TS : void 0;
51721        var timeInfo = tsInspector.inspect(data, tsStartTime);
51722        var result = null;
51723
51724        if (timeInfo) {
51725          result = {
51726            // each type's time info comes back as an array of 2 times, start and end
51727            hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
51728            hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
51729          };
51730
51731          if (result.hasVideo) {
51732            result.videoStart = timeInfo.video[0].ptsTime;
51733          }
51734
51735          if (result.hasAudio) {
51736            result.audioStart = timeInfo.audio[0].ptsTime;
51737          }
51738        }
51739
51740        this.self.postMessage({
51741          action: 'probeTs',
51742          result: result,
51743          data: data
51744        }, [data.buffer]);
51745      };
51746
51747      _proto.clearAllMp4Captions = function clearAllMp4Captions() {
51748        if (this.captionParser) {
51749          this.captionParser.clearAllCaptions();
51750        }
51751      };
51752
51753      _proto.clearParsedMp4Captions = function clearParsedMp4Captions() {
51754        if (this.captionParser) {
51755          this.captionParser.clearParsedCaptions();
51756        }
51757      }
51758      /**
51759       * Adds data (a ts segment) to the start of the transmuxer pipeline for
51760       * processing.
51761       *
51762       * @param {ArrayBuffer} data data to push into the muxer
51763       */
51764      ;
51765
51766      _proto.push = function push(data) {
51767        // Cast array buffer to correct type for transmuxer
51768        var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51769        this.transmuxer.push(segment);
51770      }
51771      /**
51772       * Recreate the transmuxer so that the next segment added via `push`
51773       * start with a fresh transmuxer.
51774       */
51775      ;
51776
51777      _proto.reset = function reset() {
51778        this.transmuxer.reset();
51779      }
51780      /**
51781       * Set the value that will be used as the `baseMediaDecodeTime` time for the
51782       * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
51783       * set relative to the first based on the PTS values.
51784       *
51785       * @param {Object} data used to set the timestamp offset in the muxer
51786       */
51787      ;
51788
51789      _proto.setTimestampOffset = function setTimestampOffset(data) {
51790        var timestampOffset = data.timestampOffset || 0;
51791        this.transmuxer.setBaseMediaDecodeTime(Math.round(clock.secondsToVideoTs(timestampOffset)));
51792      };
51793
51794      _proto.setAudioAppendStart = function setAudioAppendStart(data) {
51795        this.transmuxer.setAudioAppendStart(Math.ceil(clock.secondsToVideoTs(data.appendStart)));
51796      };
51797
51798      _proto.setRemux = function setRemux(data) {
51799        this.transmuxer.setRemux(data.remux);
51800      }
51801      /**
51802       * Forces the pipeline to finish processing the last segment and emit it's
51803       * results.
51804       *
51805       * @param {Object} data event data, not really used
51806       */
51807      ;
51808
51809      _proto.flush = function flush(data) {
51810        this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
51811
51812        self.postMessage({
51813          action: 'done',
51814          type: 'transmuxed'
51815        });
51816      };
51817
51818      _proto.endTimeline = function endTimeline() {
51819        this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
51820        // timelines
51821
51822        self.postMessage({
51823          action: 'endedtimeline',
51824          type: 'transmuxed'
51825        });
51826      };
51827
51828      _proto.alignGopsWith = function alignGopsWith(data) {
51829        this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
51830      };
51831
51832      return MessageHandlers;
51833    }();
51834    /**
51835     * Our web worker interface so that things can talk to mux.js
51836     * that will be running in a web worker. the scope is passed to this by
51837     * webworkify.
51838     *
51839     * @param {Object} self the scope for the web worker
51840     */
51841
51842
51843    self.onmessage = function (event) {
51844      if (event.data.action === 'init' && event.data.options) {
51845        this.messageHandlers = new MessageHandlers(self, event.data.options);
51846        return;
51847      }
51848
51849      if (!this.messageHandlers) {
51850        this.messageHandlers = new MessageHandlers(self);
51851      }
51852
51853      if (event.data && event.data.action && event.data.action !== 'init') {
51854        if (this.messageHandlers[event.data.action]) {
51855          this.messageHandlers[event.data.action](event.data);
51856        }
51857      }
51858    };
51859  }));
51860  var TransmuxWorker = factory(workerCode$1);
51861  /* rollup-plugin-worker-factory end for worker!/Users/bcasey/Projects/videojs-http-streaming/src/transmuxer-worker.js */
51862
51863  var handleData_ = function handleData_(event, transmuxedData, callback) {
51864    var _event$data$segment = event.data.segment,
51865        type = _event$data$segment.type,
51866        initSegment = _event$data$segment.initSegment,
51867        captions = _event$data$segment.captions,
51868        captionStreams = _event$data$segment.captionStreams,
51869        metadata = _event$data$segment.metadata,
51870        videoFrameDtsTime = _event$data$segment.videoFrameDtsTime,
51871        videoFramePtsTime = _event$data$segment.videoFramePtsTime;
51872    transmuxedData.buffer.push({
51873      captions: captions,
51874      captionStreams: captionStreams,
51875      metadata: metadata
51876    });
51877    var boxes = event.data.segment.boxes || {
51878      data: event.data.segment.data
51879    };
51880    var result = {
51881      type: type,
51882      // cast ArrayBuffer to TypedArray
51883      data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
51884      initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
51885    };
51886
51887    if (typeof videoFrameDtsTime !== 'undefined') {
51888      result.videoFrameDtsTime = videoFrameDtsTime;
51889    }
51890
51891    if (typeof videoFramePtsTime !== 'undefined') {
51892      result.videoFramePtsTime = videoFramePtsTime;
51893    }
51894
51895    callback(result);
51896  };
51897
51898  var handleDone_ = function handleDone_(_ref) {
51899    var transmuxedData = _ref.transmuxedData,
51900        callback = _ref.callback; // Previously we only returned data on data events,
51901    // not on done events. Clear out the buffer to keep that consistent.
51902
51903    transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
51904    // have received
51905
51906    callback(transmuxedData);
51907  };
51908
51909  var handleGopInfo_ = function handleGopInfo_(event, transmuxedData) {
51910    transmuxedData.gopInfo = event.data.gopInfo;
51911  };
51912
51913  var processTransmux = function processTransmux(options) {
51914    var transmuxer = options.transmuxer,
51915        bytes = options.bytes,
51916        audioAppendStart = options.audioAppendStart,
51917        gopsToAlignWith = options.gopsToAlignWith,
51918        remux = options.remux,
51919        onData = options.onData,
51920        onTrackInfo = options.onTrackInfo,
51921        onAudioTimingInfo = options.onAudioTimingInfo,
51922        onVideoTimingInfo = options.onVideoTimingInfo,
51923        onVideoSegmentTimingInfo = options.onVideoSegmentTimingInfo,
51924        onAudioSegmentTimingInfo = options.onAudioSegmentTimingInfo,
51925        onId3 = options.onId3,
51926        onCaptions = options.onCaptions,
51927        onDone = options.onDone,
51928        onEndedTimeline = options.onEndedTimeline,
51929        onTransmuxerLog = options.onTransmuxerLog,
51930        isEndOfTimeline = options.isEndOfTimeline;
51931    var transmuxedData = {
51932      buffer: []
51933    };
51934    var waitForEndedTimelineEvent = isEndOfTimeline;
51935
51936    var handleMessage = function handleMessage(event) {
51937      if (transmuxer.currentTransmux !== options) {
51938        // disposed
51939        return;
51940      }
51941
51942      if (event.data.action === 'data') {
51943        handleData_(event, transmuxedData, onData);
51944      }
51945
51946      if (event.data.action === 'trackinfo') {
51947        onTrackInfo(event.data.trackInfo);
51948      }
51949
51950      if (event.data.action === 'gopInfo') {
51951        handleGopInfo_(event, transmuxedData);
51952      }
51953
51954      if (event.data.action === 'audioTimingInfo') {
51955        onAudioTimingInfo(event.data.audioTimingInfo);
51956      }
51957
51958      if (event.data.action === 'videoTimingInfo') {
51959        onVideoTimingInfo(event.data.videoTimingInfo);
51960      }
51961
51962      if (event.data.action === 'videoSegmentTimingInfo') {
51963        onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
51964      }
51965
51966      if (event.data.action === 'audioSegmentTimingInfo') {
51967        onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
51968      }
51969
51970      if (event.data.action === 'id3Frame') {
51971        onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
51972      }
51973
51974      if (event.data.action === 'caption') {
51975        onCaptions(event.data.caption);
51976      }
51977
51978      if (event.data.action === 'endedtimeline') {
51979        waitForEndedTimelineEvent = false;
51980        onEndedTimeline();
51981      }
51982
51983      if (event.data.action === 'log') {
51984        onTransmuxerLog(event.data.log);
51985      } // wait for the transmuxed event since we may have audio and video
51986
51987
51988      if (event.data.type !== 'transmuxed') {
51989        return;
51990      } // If the "endedtimeline" event has not yet fired, and this segment represents the end
51991      // of a timeline, that means there may still be data events before the segment
51992      // processing can be considerred complete. In that case, the final event should be
51993      // an "endedtimeline" event with the type "transmuxed."
51994
51995
51996      if (waitForEndedTimelineEvent) {
51997        return;
51998      }
51999
52000      transmuxer.onmessage = null;
52001      handleDone_({
52002        transmuxedData: transmuxedData,
52003        callback: onDone
52004      });
52005      /* eslint-disable no-use-before-define */
52006
52007      dequeue(transmuxer);
52008      /* eslint-enable */
52009    };
52010
52011    transmuxer.onmessage = handleMessage;
52012
52013    if (audioAppendStart) {
52014      transmuxer.postMessage({
52015        action: 'setAudioAppendStart',
52016        appendStart: audioAppendStart
52017      });
52018    } // allow empty arrays to be passed to clear out GOPs
52019
52020
52021    if (Array.isArray(gopsToAlignWith)) {
52022      transmuxer.postMessage({
52023        action: 'alignGopsWith',
52024        gopsToAlignWith: gopsToAlignWith
52025      });
52026    }
52027
52028    if (typeof remux !== 'undefined') {
52029      transmuxer.postMessage({
52030        action: 'setRemux',
52031        remux: remux
52032      });
52033    }
52034
52035    if (bytes.byteLength) {
52036      var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
52037      var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
52038      transmuxer.postMessage({
52039        action: 'push',
52040        // Send the typed-array of data as an ArrayBuffer so that
52041        // it can be sent as a "Transferable" and avoid the costly
52042        // memory copy
52043        data: buffer,
52044        // To recreate the original typed-array, we need information
52045        // about what portion of the ArrayBuffer it was a view into
52046        byteOffset: byteOffset,
52047        byteLength: bytes.byteLength
52048      }, [buffer]);
52049    }
52050
52051    if (isEndOfTimeline) {
52052      transmuxer.postMessage({
52053        action: 'endTimeline'
52054      });
52055    } // even if we didn't push any bytes, we have to make sure we flush in case we reached
52056    // the end of the segment
52057
52058
52059    transmuxer.postMessage({
52060      action: 'flush'
52061    });
52062  };
52063
52064  var dequeue = function dequeue(transmuxer) {
52065    transmuxer.currentTransmux = null;
52066
52067    if (transmuxer.transmuxQueue.length) {
52068      transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
52069
52070      if (typeof transmuxer.currentTransmux === 'function') {
52071        transmuxer.currentTransmux();
52072      } else {
52073        processTransmux(transmuxer.currentTransmux);
52074      }
52075    }
52076  };
52077
52078  var processAction = function processAction(transmuxer, action) {
52079    transmuxer.postMessage({
52080      action: action
52081    });
52082    dequeue(transmuxer);
52083  };
52084
52085  var enqueueAction = function enqueueAction(action, transmuxer) {
52086    if (!transmuxer.currentTransmux) {
52087      transmuxer.currentTransmux = action;
52088      processAction(transmuxer, action);
52089      return;
52090    }
52091
52092    transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
52093  };
52094
52095  var reset = function reset(transmuxer) {
52096    enqueueAction('reset', transmuxer);
52097  };
52098
52099  var endTimeline = function endTimeline(transmuxer) {
52100    enqueueAction('endTimeline', transmuxer);
52101  };
52102
52103  var transmux = function transmux(options) {
52104    if (!options.transmuxer.currentTransmux) {
52105      options.transmuxer.currentTransmux = options;
52106      processTransmux(options);
52107      return;
52108    }
52109
52110    options.transmuxer.transmuxQueue.push(options);
52111  };
52112
52113  var createTransmuxer = function createTransmuxer(options) {
52114    var transmuxer = new TransmuxWorker();
52115    transmuxer.currentTransmux = null;
52116    transmuxer.transmuxQueue = [];
52117    var term = transmuxer.terminate;
52118
52119    transmuxer.terminate = function () {
52120      transmuxer.currentTransmux = null;
52121      transmuxer.transmuxQueue.length = 0;
52122      return term.call(transmuxer);
52123    };
52124
52125    transmuxer.postMessage({
52126      action: 'init',
52127      options: options
52128    });
52129    return transmuxer;
52130  };
52131
52132  var segmentTransmuxer = {
52133    reset: reset,
52134    endTimeline: endTimeline,
52135    transmux: transmux,
52136    createTransmuxer: createTransmuxer
52137  };
52138
52139  var workerCallback = function workerCallback(options) {
52140    var transmuxer = options.transmuxer;
52141    var endAction = options.endAction || options.action;
52142    var callback = options.callback;
52143
52144    var message = _extends_1({}, options, {
52145      endAction: null,
52146      transmuxer: null,
52147      callback: null
52148    });
52149
52150    var listenForEndEvent = function listenForEndEvent(event) {
52151      if (event.data.action !== endAction) {
52152        return;
52153      }
52154
52155      transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
52156
52157      if (event.data.data) {
52158        event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
52159
52160        if (options.data) {
52161          options.data = event.data.data;
52162        }
52163      }
52164
52165      callback(event.data);
52166    };
52167
52168    transmuxer.addEventListener('message', listenForEndEvent);
52169
52170    if (options.data) {
52171      var isArrayBuffer = options.data instanceof ArrayBuffer;
52172      message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
52173      message.byteLength = options.data.byteLength;
52174      var transfers = [isArrayBuffer ? options.data : options.data.buffer];
52175      transmuxer.postMessage(message, transfers);
52176    } else {
52177      transmuxer.postMessage(message);
52178    }
52179  };
52180
52181  var REQUEST_ERRORS = {
52182    FAILURE: 2,
52183    TIMEOUT: -101,
52184    ABORTED: -102
52185  };
52186  /**
52187   * Abort all requests
52188   *
52189   * @param {Object} activeXhrs - an object that tracks all XHR requests
52190   */
52191
52192  var abortAll = function abortAll(activeXhrs) {
52193    activeXhrs.forEach(function (xhr) {
52194      xhr.abort();
52195    });
52196  };
52197  /**
52198   * Gather important bandwidth stats once a request has completed
52199   *
52200   * @param {Object} request - the XHR request from which to gather stats
52201   */
52202
52203
52204  var getRequestStats = function getRequestStats(request) {
52205    return {
52206      bandwidth: request.bandwidth,
52207      bytesReceived: request.bytesReceived || 0,
52208      roundTripTime: request.roundTripTime || 0
52209    };
52210  };
52211  /**
52212   * If possible gather bandwidth stats as a request is in
52213   * progress
52214   *
52215   * @param {Event} progressEvent - an event object from an XHR's progress event
52216   */
52217
52218
52219  var getProgressStats = function getProgressStats(progressEvent) {
52220    var request = progressEvent.target;
52221    var roundTripTime = Date.now() - request.requestTime;
52222    var stats = {
52223      bandwidth: Infinity,
52224      bytesReceived: 0,
52225      roundTripTime: roundTripTime || 0
52226    };
52227    stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
52228    // because we should only use bandwidth stats on progress to determine when
52229    // abort a request early due to insufficient bandwidth
52230
52231    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
52232    return stats;
52233  };
52234  /**
52235   * Handle all error conditions in one place and return an object
52236   * with all the information
52237   *
52238   * @param {Error|null} error - if non-null signals an error occured with the XHR
52239   * @param {Object} request -  the XHR request that possibly generated the error
52240   */
52241
52242
52243  var handleErrors = function handleErrors(error, request) {
52244    if (request.timedout) {
52245      return {
52246        status: request.status,
52247        message: 'HLS request timed-out at URL: ' + request.uri,
52248        code: REQUEST_ERRORS.TIMEOUT,
52249        xhr: request
52250      };
52251    }
52252
52253    if (request.aborted) {
52254      return {
52255        status: request.status,
52256        message: 'HLS request aborted at URL: ' + request.uri,
52257        code: REQUEST_ERRORS.ABORTED,
52258        xhr: request
52259      };
52260    }
52261
52262    if (error) {
52263      return {
52264        status: request.status,
52265        message: 'HLS request errored at URL: ' + request.uri,
52266        code: REQUEST_ERRORS.FAILURE,
52267        xhr: request
52268      };
52269    }
52270
52271    if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
52272      return {
52273        status: request.status,
52274        message: 'Empty HLS response at URL: ' + request.uri,
52275        code: REQUEST_ERRORS.FAILURE,
52276        xhr: request
52277      };
52278    }
52279
52280    return null;
52281  };
52282  /**
52283   * Handle responses for key data and convert the key data to the correct format
52284   * for the decryption step later
52285   *
52286   * @param {Object} segment - a simplified copy of the segmentInfo object
52287   *                           from SegmentLoader
52288   * @param {Array} objects - objects to add the key bytes to.
52289   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52290   *                                        this request
52291   */
52292
52293
52294  var handleKeyResponse = function handleKeyResponse(segment, objects, finishProcessingFn) {
52295    return function (error, request) {
52296      var response = request.response;
52297      var errorObj = handleErrors(error, request);
52298
52299      if (errorObj) {
52300        return finishProcessingFn(errorObj, segment);
52301      }
52302
52303      if (response.byteLength !== 16) {
52304        return finishProcessingFn({
52305          status: request.status,
52306          message: 'Invalid HLS key at URL: ' + request.uri,
52307          code: REQUEST_ERRORS.FAILURE,
52308          xhr: request
52309        }, segment);
52310      }
52311
52312      var view = new DataView(response);
52313      var bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
52314
52315      for (var i = 0; i < objects.length; i++) {
52316        objects[i].bytes = bytes;
52317      }
52318
52319      return finishProcessingFn(null, segment);
52320    };
52321  };
52322
52323  var parseInitSegment = function parseInitSegment(segment, _callback) {
52324    var type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
52325    // only know how to parse mp4 init segments at the moment
52326
52327    if (type !== 'mp4') {
52328      var uri = segment.map.resolvedUri || segment.map.uri;
52329      return _callback({
52330        internal: true,
52331        message: "Found unsupported " + (type || 'unknown') + " container for initialization segment at URL: " + uri,
52332        code: REQUEST_ERRORS.FAILURE
52333      });
52334    }
52335
52336    workerCallback({
52337      action: 'probeMp4Tracks',
52338      data: segment.map.bytes,
52339      transmuxer: segment.transmuxer,
52340      callback: function callback(_ref) {
52341        var tracks = _ref.tracks,
52342            data = _ref.data; // transfer bytes back to us
52343
52344        segment.map.bytes = data;
52345        tracks.forEach(function (track) {
52346          segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
52347
52348          if (segment.map.tracks[track.type]) {
52349            return;
52350          }
52351
52352          segment.map.tracks[track.type] = track;
52353
52354          if (typeof track.id === 'number' && track.timescale) {
52355            segment.map.timescales = segment.map.timescales || {};
52356            segment.map.timescales[track.id] = track.timescale;
52357          }
52358        });
52359        return _callback(null);
52360      }
52361    });
52362  };
52363  /**
52364   * Handle init-segment responses
52365   *
52366   * @param {Object} segment - a simplified copy of the segmentInfo object
52367   *                           from SegmentLoader
52368   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52369   *                                        this request
52370   */
52371
52372
52373  var handleInitSegmentResponse = function handleInitSegmentResponse(_ref2) {
52374    var segment = _ref2.segment,
52375        finishProcessingFn = _ref2.finishProcessingFn;
52376    return function (error, request) {
52377      var errorObj = handleErrors(error, request);
52378
52379      if (errorObj) {
52380        return finishProcessingFn(errorObj, segment);
52381      }
52382
52383      var bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait
52384      // until the key request is done to decrypt.
52385
52386      if (segment.map.key) {
52387        segment.map.encryptedBytes = bytes;
52388        return finishProcessingFn(null, segment);
52389      }
52390
52391      segment.map.bytes = bytes;
52392      parseInitSegment(segment, function (parseError) {
52393        if (parseError) {
52394          parseError.xhr = request;
52395          parseError.status = request.status;
52396          return finishProcessingFn(parseError, segment);
52397        }
52398
52399        finishProcessingFn(null, segment);
52400      });
52401    };
52402  };
52403  /**
52404   * Response handler for segment-requests being sure to set the correct
52405   * property depending on whether the segment is encryped or not
52406   * Also records and keeps track of stats that are used for ABR purposes
52407   *
52408   * @param {Object} segment - a simplified copy of the segmentInfo object
52409   *                           from SegmentLoader
52410   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52411   *                                        this request
52412   */
52413
52414
52415  var handleSegmentResponse = function handleSegmentResponse(_ref3) {
52416    var segment = _ref3.segment,
52417        finishProcessingFn = _ref3.finishProcessingFn,
52418        responseType = _ref3.responseType;
52419    return function (error, request) {
52420      var errorObj = handleErrors(error, request);
52421
52422      if (errorObj) {
52423        return finishProcessingFn(errorObj, segment);
52424      }
52425
52426      var newBytes = // although responseText "should" exist, this guard serves to prevent an error being
52427      // thrown for two primary cases:
52428      // 1. the mime type override stops working, or is not implemented for a specific
52429      //    browser
52430      // 2. when using mock XHR libraries like sinon that do not allow the override behavior
52431      responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
52432      segment.stats = getRequestStats(request);
52433
52434      if (segment.key) {
52435        segment.encryptedBytes = new Uint8Array(newBytes);
52436      } else {
52437        segment.bytes = new Uint8Array(newBytes);
52438      }
52439
52440      return finishProcessingFn(null, segment);
52441    };
52442  };
52443
52444  var transmuxAndNotify = function transmuxAndNotify(_ref4) {
52445    var segment = _ref4.segment,
52446        bytes = _ref4.bytes,
52447        trackInfoFn = _ref4.trackInfoFn,
52448        timingInfoFn = _ref4.timingInfoFn,
52449        videoSegmentTimingInfoFn = _ref4.videoSegmentTimingInfoFn,
52450        audioSegmentTimingInfoFn = _ref4.audioSegmentTimingInfoFn,
52451        id3Fn = _ref4.id3Fn,
52452        captionsFn = _ref4.captionsFn,
52453        isEndOfTimeline = _ref4.isEndOfTimeline,
52454        endedTimelineFn = _ref4.endedTimelineFn,
52455        dataFn = _ref4.dataFn,
52456        doneFn = _ref4.doneFn,
52457        onTransmuxerLog = _ref4.onTransmuxerLog;
52458    var fmp4Tracks = segment.map && segment.map.tracks || {};
52459    var isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
52460    // One reason for this is that in the case of full segments, we want to trust start
52461    // times from the probe, rather than the transmuxer.
52462
52463    var audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
52464    var audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
52465    var videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
52466    var videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
52467
52468    var finish = function finish() {
52469      return transmux({
52470        bytes: bytes,
52471        transmuxer: segment.transmuxer,
52472        audioAppendStart: segment.audioAppendStart,
52473        gopsToAlignWith: segment.gopsToAlignWith,
52474        remux: isMuxed,
52475        onData: function onData(result) {
52476          result.type = result.type === 'combined' ? 'video' : result.type;
52477          dataFn(segment, result);
52478        },
52479        onTrackInfo: function onTrackInfo(trackInfo) {
52480          if (trackInfoFn) {
52481            if (isMuxed) {
52482              trackInfo.isMuxed = true;
52483            }
52484
52485            trackInfoFn(segment, trackInfo);
52486          }
52487        },
52488        onAudioTimingInfo: function onAudioTimingInfo(audioTimingInfo) {
52489          // we only want the first start value we encounter
52490          if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
52491            audioStartFn(audioTimingInfo.start);
52492            audioStartFn = null;
52493          } // we want to continually update the end time
52494
52495
52496          if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
52497            audioEndFn(audioTimingInfo.end);
52498          }
52499        },
52500        onVideoTimingInfo: function onVideoTimingInfo(videoTimingInfo) {
52501          // we only want the first start value we encounter
52502          if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
52503            videoStartFn(videoTimingInfo.start);
52504            videoStartFn = null;
52505          } // we want to continually update the end time
52506
52507
52508          if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
52509            videoEndFn(videoTimingInfo.end);
52510          }
52511        },
52512        onVideoSegmentTimingInfo: function onVideoSegmentTimingInfo(videoSegmentTimingInfo) {
52513          videoSegmentTimingInfoFn(videoSegmentTimingInfo);
52514        },
52515        onAudioSegmentTimingInfo: function onAudioSegmentTimingInfo(audioSegmentTimingInfo) {
52516          audioSegmentTimingInfoFn(audioSegmentTimingInfo);
52517        },
52518        onId3: function onId3(id3Frames, dispatchType) {
52519          id3Fn(segment, id3Frames, dispatchType);
52520        },
52521        onCaptions: function onCaptions(captions) {
52522          captionsFn(segment, [captions]);
52523        },
52524        isEndOfTimeline: isEndOfTimeline,
52525        onEndedTimeline: function onEndedTimeline() {
52526          endedTimelineFn();
52527        },
52528        onTransmuxerLog: onTransmuxerLog,
52529        onDone: function onDone(result) {
52530          if (!doneFn) {
52531            return;
52532          }
52533
52534          result.type = result.type === 'combined' ? 'video' : result.type;
52535          doneFn(null, segment, result);
52536        }
52537      });
52538    }; // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
52539    // Meaning cached frame data may corrupt our notion of where this segment
52540    // really starts. To get around this, probe for the info needed.
52541
52542
52543    workerCallback({
52544      action: 'probeTs',
52545      transmuxer: segment.transmuxer,
52546      data: bytes,
52547      baseStartTime: segment.baseStartTime,
52548      callback: function callback(data) {
52549        segment.bytes = bytes = data.data;
52550        var probeResult = data.result;
52551
52552        if (probeResult) {
52553          trackInfoFn(segment, {
52554            hasAudio: probeResult.hasAudio,
52555            hasVideo: probeResult.hasVideo,
52556            isMuxed: isMuxed
52557          });
52558          trackInfoFn = null;
52559
52560          if (probeResult.hasAudio && !isMuxed) {
52561            audioStartFn(probeResult.audioStart);
52562          }
52563
52564          if (probeResult.hasVideo) {
52565            videoStartFn(probeResult.videoStart);
52566          }
52567
52568          audioStartFn = null;
52569          videoStartFn = null;
52570        }
52571
52572        finish();
52573      }
52574    });
52575  };
52576
52577  var handleSegmentBytes = function handleSegmentBytes(_ref5) {
52578    var segment = _ref5.segment,
52579        bytes = _ref5.bytes,
52580        trackInfoFn = _ref5.trackInfoFn,
52581        timingInfoFn = _ref5.timingInfoFn,
52582        videoSegmentTimingInfoFn = _ref5.videoSegmentTimingInfoFn,
52583        audioSegmentTimingInfoFn = _ref5.audioSegmentTimingInfoFn,
52584        id3Fn = _ref5.id3Fn,
52585        captionsFn = _ref5.captionsFn,
52586        isEndOfTimeline = _ref5.isEndOfTimeline,
52587        endedTimelineFn = _ref5.endedTimelineFn,
52588        dataFn = _ref5.dataFn,
52589        doneFn = _ref5.doneFn,
52590        onTransmuxerLog = _ref5.onTransmuxerLog;
52591    var bytesAsUint8Array = new Uint8Array(bytes); // TODO:
52592    // We should have a handler that fetches the number of bytes required
52593    // to check if something is fmp4. This will allow us to save bandwidth
52594    // because we can only blacklist a playlist and abort requests
52595    // by codec after trackinfo triggers.
52596
52597    if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
52598      segment.isFmp4 = true;
52599      var tracks = segment.map.tracks;
52600      var trackInfo = {
52601        isFmp4: true,
52602        hasVideo: !!tracks.video,
52603        hasAudio: !!tracks.audio
52604      }; // if we have a audio track, with a codec that is not set to
52605      // encrypted audio
52606
52607      if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
52608        trackInfo.audioCodec = tracks.audio.codec;
52609      } // if we have a video track, with a codec that is not set to
52610      // encrypted video
52611
52612
52613      if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
52614        trackInfo.videoCodec = tracks.video.codec;
52615      }
52616
52617      if (tracks.video && tracks.audio) {
52618        trackInfo.isMuxed = true;
52619      } // since we don't support appending fmp4 data on progress, we know we have the full
52620      // segment here
52621
52622
52623      trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
52624      // time. The end time can be roughly calculated by the receiver using the duration.
52625      //
52626      // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
52627      // that is the true start of the segment (where the playback engine should begin
52628      // decoding).
52629
52630      var finishLoading = function finishLoading(captions) {
52631        // if the track still has audio at this point it is only possible
52632        // for it to be audio only. See `tracks.video && tracks.audio` if statement
52633        // above.
52634        // we make sure to use segment.bytes here as that
52635        dataFn(segment, {
52636          data: bytesAsUint8Array,
52637          type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
52638        });
52639
52640        if (captions && captions.length) {
52641          captionsFn(segment, captions);
52642        }
52643
52644        doneFn(null, segment, {});
52645      };
52646
52647      workerCallback({
52648        action: 'probeMp4StartTime',
52649        timescales: segment.map.timescales,
52650        data: bytesAsUint8Array,
52651        transmuxer: segment.transmuxer,
52652        callback: function callback(_ref6) {
52653          var data = _ref6.data,
52654              startTime = _ref6.startTime; // transfer bytes back to us
52655
52656          bytes = data.buffer;
52657          segment.bytes = bytesAsUint8Array = data;
52658
52659          if (trackInfo.hasAudio && !trackInfo.isMuxed) {
52660            timingInfoFn(segment, 'audio', 'start', startTime);
52661          }
52662
52663          if (trackInfo.hasVideo) {
52664            timingInfoFn(segment, 'video', 'start', startTime);
52665          } // Run through the CaptionParser in case there are captions.
52666          // Initialize CaptionParser if it hasn't been yet
52667
52668
52669          if (!tracks.video || !data.byteLength || !segment.transmuxer) {
52670            finishLoading();
52671            return;
52672          }
52673
52674          workerCallback({
52675            action: 'pushMp4Captions',
52676            endAction: 'mp4Captions',
52677            transmuxer: segment.transmuxer,
52678            data: bytesAsUint8Array,
52679            timescales: segment.map.timescales,
52680            trackIds: [tracks.video.id],
52681            callback: function callback(message) {
52682              // transfer bytes back to us
52683              bytes = message.data.buffer;
52684              segment.bytes = bytesAsUint8Array = message.data;
52685              message.logs.forEach(function (log) {
52686                onTransmuxerLog(videojs.mergeOptions(log, {
52687                  stream: 'mp4CaptionParser'
52688                }));
52689              });
52690              finishLoading(message.captions);
52691            }
52692          });
52693        }
52694      });
52695      return;
52696    } // VTT or other segments that don't need processing
52697
52698
52699    if (!segment.transmuxer) {
52700      doneFn(null, segment, {});
52701      return;
52702    }
52703
52704    if (typeof segment.container === 'undefined') {
52705      segment.container = detectContainerForBytes(bytesAsUint8Array);
52706    }
52707
52708    if (segment.container !== 'ts' && segment.container !== 'aac') {
52709      trackInfoFn(segment, {
52710        hasAudio: false,
52711        hasVideo: false
52712      });
52713      doneFn(null, segment, {});
52714      return;
52715    } // ts or aac
52716
52717
52718    transmuxAndNotify({
52719      segment: segment,
52720      bytes: bytes,
52721      trackInfoFn: trackInfoFn,
52722      timingInfoFn: timingInfoFn,
52723      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
52724      audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
52725      id3Fn: id3Fn,
52726      captionsFn: captionsFn,
52727      isEndOfTimeline: isEndOfTimeline,
52728      endedTimelineFn: endedTimelineFn,
52729      dataFn: dataFn,
52730      doneFn: doneFn,
52731      onTransmuxerLog: onTransmuxerLog
52732    });
52733  };
52734
52735  var decrypt = function decrypt(_ref7, callback) {
52736    var id = _ref7.id,
52737        key = _ref7.key,
52738        encryptedBytes = _ref7.encryptedBytes,
52739        decryptionWorker = _ref7.decryptionWorker;
52740
52741    var decryptionHandler = function decryptionHandler(event) {
52742      if (event.data.source === id) {
52743        decryptionWorker.removeEventListener('message', decryptionHandler);
52744        var decrypted = event.data.decrypted;
52745        callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
52746      }
52747    };
52748
52749    decryptionWorker.addEventListener('message', decryptionHandler);
52750    var keyBytes;
52751
52752    if (key.bytes.slice) {
52753      keyBytes = key.bytes.slice();
52754    } else {
52755      keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
52756    } // incrementally decrypt the bytes
52757
52758
52759    decryptionWorker.postMessage(createTransferableMessage({
52760      source: id,
52761      encrypted: encryptedBytes,
52762      key: keyBytes,
52763      iv: key.iv
52764    }), [encryptedBytes.buffer, keyBytes.buffer]);
52765  };
52766  /**
52767   * Decrypt the segment via the decryption web worker
52768   *
52769   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52770   *                                       routines
52771   * @param {Object} segment - a simplified copy of the segmentInfo object
52772   *                           from SegmentLoader
52773   * @param {Function} trackInfoFn - a callback that receives track info
52774   * @param {Function} timingInfoFn - a callback that receives timing info
52775   * @param {Function} videoSegmentTimingInfoFn
52776   *                   a callback that receives video timing info based on media times and
52777   *                   any adjustments made by the transmuxer
52778   * @param {Function} audioSegmentTimingInfoFn
52779   *                   a callback that receives audio timing info based on media times and
52780   *                   any adjustments made by the transmuxer
52781   * @param {boolean}  isEndOfTimeline
52782   *                   true if this segment represents the last segment in a timeline
52783   * @param {Function} endedTimelineFn
52784   *                   a callback made when a timeline is ended, will only be called if
52785   *                   isEndOfTimeline is true
52786   * @param {Function} dataFn - a callback that is executed when segment bytes are available
52787   *                            and ready to use
52788   * @param {Function} doneFn - a callback that is executed after decryption has completed
52789   */
52790
52791
52792  var decryptSegment = function decryptSegment(_ref8) {
52793    var decryptionWorker = _ref8.decryptionWorker,
52794        segment = _ref8.segment,
52795        trackInfoFn = _ref8.trackInfoFn,
52796        timingInfoFn = _ref8.timingInfoFn,
52797        videoSegmentTimingInfoFn = _ref8.videoSegmentTimingInfoFn,
52798        audioSegmentTimingInfoFn = _ref8.audioSegmentTimingInfoFn,
52799        id3Fn = _ref8.id3Fn,
52800        captionsFn = _ref8.captionsFn,
52801        isEndOfTimeline = _ref8.isEndOfTimeline,
52802        endedTimelineFn = _ref8.endedTimelineFn,
52803        dataFn = _ref8.dataFn,
52804        doneFn = _ref8.doneFn,
52805        onTransmuxerLog = _ref8.onTransmuxerLog;
52806    decrypt({
52807      id: segment.requestId,
52808      key: segment.key,
52809      encryptedBytes: segment.encryptedBytes,
52810      decryptionWorker: decryptionWorker
52811    }, function (decryptedBytes) {
52812      segment.bytes = decryptedBytes;
52813      handleSegmentBytes({
52814        segment: segment,
52815        bytes: segment.bytes,
52816        trackInfoFn: trackInfoFn,
52817        timingInfoFn: timingInfoFn,
52818        videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
52819        audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
52820        id3Fn: id3Fn,
52821        captionsFn: captionsFn,
52822        isEndOfTimeline: isEndOfTimeline,
52823        endedTimelineFn: endedTimelineFn,
52824        dataFn: dataFn,
52825        doneFn: doneFn,
52826        onTransmuxerLog: onTransmuxerLog
52827      });
52828    });
52829  };
52830  /**
52831   * This function waits for all XHRs to finish (with either success or failure)
52832   * before continueing processing via it's callback. The function gathers errors
52833   * from each request into a single errors array so that the error status for
52834   * each request can be examined later.
52835   *
52836   * @param {Object} activeXhrs - an object that tracks all XHR requests
52837   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52838   *                                       routines
52839   * @param {Function} trackInfoFn - a callback that receives track info
52840   * @param {Function} timingInfoFn - a callback that receives timing info
52841   * @param {Function} videoSegmentTimingInfoFn
52842   *                   a callback that receives video timing info based on media times and
52843   *                   any adjustments made by the transmuxer
52844   * @param {Function} audioSegmentTimingInfoFn
52845   *                   a callback that receives audio timing info based on media times and
52846   *                   any adjustments made by the transmuxer
52847   * @param {Function} id3Fn - a callback that receives ID3 metadata
52848   * @param {Function} captionsFn - a callback that receives captions
52849   * @param {boolean}  isEndOfTimeline
52850   *                   true if this segment represents the last segment in a timeline
52851   * @param {Function} endedTimelineFn
52852   *                   a callback made when a timeline is ended, will only be called if
52853   *                   isEndOfTimeline is true
52854   * @param {Function} dataFn - a callback that is executed when segment bytes are available
52855   *                            and ready to use
52856   * @param {Function} doneFn - a callback that is executed after all resources have been
52857   *                            downloaded and any decryption completed
52858   */
52859
52860
52861  var waitForCompletion = function waitForCompletion(_ref9) {
52862    var activeXhrs = _ref9.activeXhrs,
52863        decryptionWorker = _ref9.decryptionWorker,
52864        trackInfoFn = _ref9.trackInfoFn,
52865        timingInfoFn = _ref9.timingInfoFn,
52866        videoSegmentTimingInfoFn = _ref9.videoSegmentTimingInfoFn,
52867        audioSegmentTimingInfoFn = _ref9.audioSegmentTimingInfoFn,
52868        id3Fn = _ref9.id3Fn,
52869        captionsFn = _ref9.captionsFn,
52870        isEndOfTimeline = _ref9.isEndOfTimeline,
52871        endedTimelineFn = _ref9.endedTimelineFn,
52872        dataFn = _ref9.dataFn,
52873        doneFn = _ref9.doneFn,
52874        onTransmuxerLog = _ref9.onTransmuxerLog;
52875    var count = 0;
52876    var didError = false;
52877    return function (error, segment) {
52878      if (didError) {
52879        return;
52880      }
52881
52882      if (error) {
52883        didError = true; // If there are errors, we have to abort any outstanding requests
52884
52885        abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
52886        // handle the aborted events from those requests, there are some cases where we may
52887        // never get an aborted event. For instance, if the network connection is lost and
52888        // there were two requests, the first may have triggered an error immediately, while
52889        // the second request remains unsent. In that case, the aborted algorithm will not
52890        // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
52891        //
52892        // We also can't rely on the ready state of the XHR, since the request that
52893        // triggered the connection error may also show as a ready state of 0 (unsent).
52894        // Therefore, we have to finish this group of requests immediately after the first
52895        // seen error.
52896
52897        return doneFn(error, segment);
52898      }
52899
52900      count += 1;
52901
52902      if (count === activeXhrs.length) {
52903        var segmentFinish = function segmentFinish() {
52904          if (segment.encryptedBytes) {
52905            return decryptSegment({
52906              decryptionWorker: decryptionWorker,
52907              segment: segment,
52908              trackInfoFn: trackInfoFn,
52909              timingInfoFn: timingInfoFn,
52910              videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
52911              audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
52912              id3Fn: id3Fn,
52913              captionsFn: captionsFn,
52914              isEndOfTimeline: isEndOfTimeline,
52915              endedTimelineFn: endedTimelineFn,
52916              dataFn: dataFn,
52917              doneFn: doneFn,
52918              onTransmuxerLog: onTransmuxerLog
52919            });
52920          } // Otherwise, everything is ready just continue
52921
52922
52923          handleSegmentBytes({
52924            segment: segment,
52925            bytes: segment.bytes,
52926            trackInfoFn: trackInfoFn,
52927            timingInfoFn: timingInfoFn,
52928            videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
52929            audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
52930            id3Fn: id3Fn,
52931            captionsFn: captionsFn,
52932            isEndOfTimeline: isEndOfTimeline,
52933            endedTimelineFn: endedTimelineFn,
52934            dataFn: dataFn,
52935            doneFn: doneFn,
52936            onTransmuxerLog: onTransmuxerLog
52937          });
52938        }; // Keep track of when *all* of the requests have completed
52939
52940
52941        segment.endOfAllRequests = Date.now();
52942
52943        if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
52944          return decrypt({
52945            decryptionWorker: decryptionWorker,
52946            // add -init to the "id" to differentiate between segment
52947            // and init segment decryption, just in case they happen
52948            // at the same time at some point in the future.
52949            id: segment.requestId + '-init',
52950            encryptedBytes: segment.map.encryptedBytes,
52951            key: segment.map.key
52952          }, function (decryptedBytes) {
52953            segment.map.bytes = decryptedBytes;
52954            parseInitSegment(segment, function (parseError) {
52955              if (parseError) {
52956                abortAll(activeXhrs);
52957                return doneFn(parseError, segment);
52958              }
52959
52960              segmentFinish();
52961            });
52962          });
52963        }
52964
52965        segmentFinish();
52966      }
52967    };
52968  };
52969  /**
52970   * Calls the abort callback if any request within the batch was aborted. Will only call
52971   * the callback once per batch of requests, even if multiple were aborted.
52972   *
52973   * @param {Object} loadendState - state to check to see if the abort function was called
52974   * @param {Function} abortFn - callback to call for abort
52975   */
52976
52977
52978  var handleLoadEnd = function handleLoadEnd(_ref10) {
52979    var loadendState = _ref10.loadendState,
52980        abortFn = _ref10.abortFn;
52981    return function (event) {
52982      var request = event.target;
52983
52984      if (request.aborted && abortFn && !loadendState.calledAbortFn) {
52985        abortFn();
52986        loadendState.calledAbortFn = true;
52987      }
52988    };
52989  };
52990  /**
52991   * Simple progress event callback handler that gathers some stats before
52992   * executing a provided callback with the `segment` object
52993   *
52994   * @param {Object} segment - a simplified copy of the segmentInfo object
52995   *                           from SegmentLoader
52996   * @param {Function} progressFn - a callback that is executed each time a progress event
52997   *                                is received
52998   * @param {Function} trackInfoFn - a callback that receives track info
52999   * @param {Function} timingInfoFn - a callback that receives timing info
53000   * @param {Function} videoSegmentTimingInfoFn
53001   *                   a callback that receives video timing info based on media times and
53002   *                   any adjustments made by the transmuxer
53003   * @param {Function} audioSegmentTimingInfoFn
53004   *                   a callback that receives audio timing info based on media times and
53005   *                   any adjustments made by the transmuxer
53006   * @param {boolean}  isEndOfTimeline
53007   *                   true if this segment represents the last segment in a timeline
53008   * @param {Function} endedTimelineFn
53009   *                   a callback made when a timeline is ended, will only be called if
53010   *                   isEndOfTimeline is true
53011   * @param {Function} dataFn - a callback that is executed when segment bytes are available
53012   *                            and ready to use
53013   * @param {Event} event - the progress event object from XMLHttpRequest
53014   */
53015
53016
53017  var handleProgress = function handleProgress(_ref11) {
53018    var segment = _ref11.segment,
53019        progressFn = _ref11.progressFn;
53020    _ref11.trackInfoFn;
53021    _ref11.timingInfoFn;
53022    _ref11.videoSegmentTimingInfoFn;
53023    _ref11.audioSegmentTimingInfoFn;
53024    _ref11.id3Fn;
53025    _ref11.captionsFn;
53026    _ref11.isEndOfTimeline;
53027    _ref11.endedTimelineFn;
53028    _ref11.dataFn;
53029    return function (event) {
53030      var request = event.target;
53031
53032      if (request.aborted) {
53033        return;
53034      }
53035
53036      segment.stats = videojs.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
53037
53038      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
53039        segment.stats.firstBytesReceivedAt = Date.now();
53040      }
53041
53042      return progressFn(event, segment);
53043    };
53044  };
53045  /**
53046   * Load all resources and does any processing necessary for a media-segment
53047   *
53048   * Features:
53049   *   decrypts the media-segment if it has a key uri and an iv
53050   *   aborts *all* requests if *any* one request fails
53051   *
53052   * The segment object, at minimum, has the following format:
53053   * {
53054   *   resolvedUri: String,
53055   *   [transmuxer]: Object,
53056   *   [byterange]: {
53057   *     offset: Number,
53058   *     length: Number
53059   *   },
53060   *   [key]: {
53061   *     resolvedUri: String
53062   *     [byterange]: {
53063   *       offset: Number,
53064   *       length: Number
53065   *     },
53066   *     iv: {
53067   *       bytes: Uint32Array
53068   *     }
53069   *   },
53070   *   [map]: {
53071   *     resolvedUri: String,
53072   *     [byterange]: {
53073   *       offset: Number,
53074   *       length: Number
53075   *     },
53076   *     [bytes]: Uint8Array
53077   *   }
53078   * }
53079   * ...where [name] denotes optional properties
53080   *
53081   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
53082   * @param {Object} xhrOptions - the base options to provide to all xhr requests
53083   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
53084   *                                       decryption routines
53085   * @param {Object} segment - a simplified copy of the segmentInfo object
53086   *                           from SegmentLoader
53087   * @param {Function} abortFn - a callback called (only once) if any piece of a request was
53088   *                             aborted
53089   * @param {Function} progressFn - a callback that receives progress events from the main
53090   *                                segment's xhr request
53091   * @param {Function} trackInfoFn - a callback that receives track info
53092   * @param {Function} timingInfoFn - a callback that receives timing info
53093   * @param {Function} videoSegmentTimingInfoFn
53094   *                   a callback that receives video timing info based on media times and
53095   *                   any adjustments made by the transmuxer
53096   * @param {Function} audioSegmentTimingInfoFn
53097   *                   a callback that receives audio timing info based on media times and
53098   *                   any adjustments made by the transmuxer
53099   * @param {Function} id3Fn - a callback that receives ID3 metadata
53100   * @param {Function} captionsFn - a callback that receives captions
53101   * @param {boolean}  isEndOfTimeline
53102   *                   true if this segment represents the last segment in a timeline
53103   * @param {Function} endedTimelineFn
53104   *                   a callback made when a timeline is ended, will only be called if
53105   *                   isEndOfTimeline is true
53106   * @param {Function} dataFn - a callback that receives data from the main segment's xhr
53107   *                            request, transmuxed if needed
53108   * @param {Function} doneFn - a callback that is executed only once all requests have
53109   *                            succeeded or failed
53110   * @return {Function} a function that, when invoked, immediately aborts all
53111   *                     outstanding requests
53112   */
53113
53114
53115  var mediaSegmentRequest = function mediaSegmentRequest(_ref12) {
53116    var xhr = _ref12.xhr,
53117        xhrOptions = _ref12.xhrOptions,
53118        decryptionWorker = _ref12.decryptionWorker,
53119        segment = _ref12.segment,
53120        abortFn = _ref12.abortFn,
53121        progressFn = _ref12.progressFn,
53122        trackInfoFn = _ref12.trackInfoFn,
53123        timingInfoFn = _ref12.timingInfoFn,
53124        videoSegmentTimingInfoFn = _ref12.videoSegmentTimingInfoFn,
53125        audioSegmentTimingInfoFn = _ref12.audioSegmentTimingInfoFn,
53126        id3Fn = _ref12.id3Fn,
53127        captionsFn = _ref12.captionsFn,
53128        isEndOfTimeline = _ref12.isEndOfTimeline,
53129        endedTimelineFn = _ref12.endedTimelineFn,
53130        dataFn = _ref12.dataFn,
53131        doneFn = _ref12.doneFn,
53132        onTransmuxerLog = _ref12.onTransmuxerLog;
53133    var activeXhrs = [];
53134    var finishProcessingFn = waitForCompletion({
53135      activeXhrs: activeXhrs,
53136      decryptionWorker: decryptionWorker,
53137      trackInfoFn: trackInfoFn,
53138      timingInfoFn: timingInfoFn,
53139      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
53140      audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
53141      id3Fn: id3Fn,
53142      captionsFn: captionsFn,
53143      isEndOfTimeline: isEndOfTimeline,
53144      endedTimelineFn: endedTimelineFn,
53145      dataFn: dataFn,
53146      doneFn: doneFn,
53147      onTransmuxerLog: onTransmuxerLog
53148    }); // optionally, request the decryption key
53149
53150    if (segment.key && !segment.key.bytes) {
53151      var objects = [segment.key];
53152
53153      if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
53154        objects.push(segment.map.key);
53155      }
53156
53157      var keyRequestOptions = videojs.mergeOptions(xhrOptions, {
53158        uri: segment.key.resolvedUri,
53159        responseType: 'arraybuffer'
53160      });
53161      var keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn);
53162      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
53163      activeXhrs.push(keyXhr);
53164    } // optionally, request the associated media init segment
53165
53166
53167    if (segment.map && !segment.map.bytes) {
53168      var differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
53169
53170      if (differentMapKey) {
53171        var mapKeyRequestOptions = videojs.mergeOptions(xhrOptions, {
53172          uri: segment.map.key.resolvedUri,
53173          responseType: 'arraybuffer'
53174        });
53175        var mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn);
53176        var mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
53177        activeXhrs.push(mapKeyXhr);
53178      }
53179
53180      var initSegmentOptions = videojs.mergeOptions(xhrOptions, {
53181        uri: segment.map.resolvedUri,
53182        responseType: 'arraybuffer',
53183        headers: segmentXhrHeaders(segment.map)
53184      });
53185      var initSegmentRequestCallback = handleInitSegmentResponse({
53186        segment: segment,
53187        finishProcessingFn: finishProcessingFn
53188      });
53189      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
53190      activeXhrs.push(initSegmentXhr);
53191    }
53192
53193    var segmentRequestOptions = videojs.mergeOptions(xhrOptions, {
53194      uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
53195      responseType: 'arraybuffer',
53196      headers: segmentXhrHeaders(segment)
53197    });
53198    var segmentRequestCallback = handleSegmentResponse({
53199      segment: segment,
53200      finishProcessingFn: finishProcessingFn,
53201      responseType: segmentRequestOptions.responseType
53202    });
53203    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
53204    segmentXhr.addEventListener('progress', handleProgress({
53205      segment: segment,
53206      progressFn: progressFn,
53207      trackInfoFn: trackInfoFn,
53208      timingInfoFn: timingInfoFn,
53209      videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
53210      audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
53211      id3Fn: id3Fn,
53212      captionsFn: captionsFn,
53213      isEndOfTimeline: isEndOfTimeline,
53214      endedTimelineFn: endedTimelineFn,
53215      dataFn: dataFn
53216    }));
53217    activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
53218    // multiple times, provide a shared state object
53219
53220    var loadendState = {};
53221    activeXhrs.forEach(function (activeXhr) {
53222      activeXhr.addEventListener('loadend', handleLoadEnd({
53223        loadendState: loadendState,
53224        abortFn: abortFn
53225      }));
53226    });
53227    return function () {
53228      return abortAll(activeXhrs);
53229    };
53230  };
53231  /**
53232   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
53233   * codec strings, or translating codec strings into objects that can be examined.
53234   */
53235
53236
53237  var logFn$1 = logger('CodecUtils');
53238  /**
53239   * Returns a set of codec strings parsed from the playlist or the default
53240   * codec strings if no codecs were specified in the playlist
53241   *
53242   * @param {Playlist} media the current media playlist
53243   * @return {Object} an object with the video and audio codecs
53244   */
53245
53246  var getCodecs = function getCodecs(media) {
53247    // if the codecs were explicitly specified, use them instead of the
53248    // defaults
53249    var mediaAttributes = media.attributes || {};
53250
53251    if (mediaAttributes.CODECS) {
53252      return parseCodecs(mediaAttributes.CODECS);
53253    }
53254  };
53255
53256  var isMaat = function isMaat(master, media) {
53257    var mediaAttributes = media.attributes || {};
53258    return master && master.mediaGroups && master.mediaGroups.AUDIO && mediaAttributes.AUDIO && master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
53259  };
53260
53261  var isMuxed = function isMuxed(master, media) {
53262    if (!isMaat(master, media)) {
53263      return true;
53264    }
53265
53266    var mediaAttributes = media.attributes || {};
53267    var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
53268
53269    for (var groupId in audioGroup) {
53270      // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
53271      // or there are listed playlists (the case for DASH, as the manifest will have already
53272      // provided all of the details necessary to generate the audio playlist, as opposed to
53273      // HLS' externally requested playlists), then the content is demuxed.
53274      if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
53275        return true;
53276      }
53277    }
53278
53279    return false;
53280  };
53281
53282  var unwrapCodecList = function unwrapCodecList(codecList) {
53283    var codecs = {};
53284    codecList.forEach(function (_ref) {
53285      var mediaType = _ref.mediaType,
53286          type = _ref.type,
53287          details = _ref.details;
53288      codecs[mediaType] = codecs[mediaType] || [];
53289      codecs[mediaType].push(translateLegacyCodec("" + type + details));
53290    });
53291    Object.keys(codecs).forEach(function (mediaType) {
53292      if (codecs[mediaType].length > 1) {
53293        logFn$1("multiple " + mediaType + " codecs found as attributes: " + codecs[mediaType].join(', ') + ". Setting playlist codecs to null so that we wait for mux.js to probe segments for real codecs.");
53294        codecs[mediaType] = null;
53295        return;
53296      }
53297
53298      codecs[mediaType] = codecs[mediaType][0];
53299    });
53300    return codecs;
53301  };
53302
53303  var codecCount = function codecCount(codecObj) {
53304    var count = 0;
53305
53306    if (codecObj.audio) {
53307      count++;
53308    }
53309
53310    if (codecObj.video) {
53311      count++;
53312    }
53313
53314    return count;
53315  };
53316  /**
53317   * Calculates the codec strings for a working configuration of
53318   * SourceBuffers to play variant streams in a master playlist. If
53319   * there is no possible working configuration, an empty object will be
53320   * returned.
53321   *
53322   * @param master {Object} the m3u8 object for the master playlist
53323   * @param media {Object} the m3u8 object for the variant playlist
53324   * @return {Object} the codec strings.
53325   *
53326   * @private
53327   */
53328
53329
53330  var codecsForPlaylist = function codecsForPlaylist(master, media) {
53331    var mediaAttributes = media.attributes || {};
53332    var codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
53333    // Put another way, there is no way to have a video-only multiple-audio HLS!
53334
53335    if (isMaat(master, media) && !codecInfo.audio) {
53336      if (!isMuxed(master, media)) {
53337        // It is possible for codecs to be specified on the audio media group playlist but
53338        // not on the rendition playlist. This is mostly the case for DASH, where audio and
53339        // video are always separate (and separately specified).
53340        var defaultCodecs = unwrapCodecList(codecsFromDefault(master, mediaAttributes.AUDIO) || []);
53341
53342        if (defaultCodecs.audio) {
53343          codecInfo.audio = defaultCodecs.audio;
53344        }
53345      }
53346    }
53347
53348    return codecInfo;
53349  };
53350
53351  var logFn = logger('PlaylistSelector');
53352
53353  var representationToString = function representationToString(representation) {
53354    if (!representation || !representation.playlist) {
53355      return;
53356    }
53357
53358    var playlist = representation.playlist;
53359    return JSON.stringify({
53360      id: playlist.id,
53361      bandwidth: representation.bandwidth,
53362      width: representation.width,
53363      height: representation.height,
53364      codecs: playlist.attributes && playlist.attributes.CODECS || ''
53365    });
53366  }; // Utilities
53367
53368  /**
53369   * Returns the CSS value for the specified property on an element
53370   * using `getComputedStyle`. Firefox has a long-standing issue where
53371   * getComputedStyle() may return null when running in an iframe with
53372   * `display: none`.
53373   *
53374   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
53375   * @param {HTMLElement} el the htmlelement to work on
53376   * @param {string} the proprety to get the style for
53377   */
53378
53379
53380  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
53381    if (!el) {
53382      return '';
53383    }
53384
53385    var result = window_1.getComputedStyle(el);
53386
53387    if (!result) {
53388      return '';
53389    }
53390
53391    return result[property];
53392  };
53393  /**
53394   * Resuable stable sort function
53395   *
53396   * @param {Playlists} array
53397   * @param {Function} sortFn Different comparators
53398   * @function stableSort
53399   */
53400
53401
53402  var stableSort = function stableSort(array, sortFn) {
53403    var newArray = array.slice();
53404    array.sort(function (left, right) {
53405      var cmp = sortFn(left, right);
53406
53407      if (cmp === 0) {
53408        return newArray.indexOf(left) - newArray.indexOf(right);
53409      }
53410
53411      return cmp;
53412    });
53413  };
53414  /**
53415   * A comparator function to sort two playlist object by bandwidth.
53416   *
53417   * @param {Object} left a media playlist object
53418   * @param {Object} right a media playlist object
53419   * @return {number} Greater than zero if the bandwidth attribute of
53420   * left is greater than the corresponding attribute of right. Less
53421   * than zero if the bandwidth of right is greater than left and
53422   * exactly zero if the two are equal.
53423   */
53424
53425
53426  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
53427    var leftBandwidth;
53428    var rightBandwidth;
53429
53430    if (left.attributes.BANDWIDTH) {
53431      leftBandwidth = left.attributes.BANDWIDTH;
53432    }
53433
53434    leftBandwidth = leftBandwidth || window_1.Number.MAX_VALUE;
53435
53436    if (right.attributes.BANDWIDTH) {
53437      rightBandwidth = right.attributes.BANDWIDTH;
53438    }
53439
53440    rightBandwidth = rightBandwidth || window_1.Number.MAX_VALUE;
53441    return leftBandwidth - rightBandwidth;
53442  };
53443  /**
53444   * A comparator function to sort two playlist object by resolution (width).
53445   *
53446   * @param {Object} left a media playlist object
53447   * @param {Object} right a media playlist object
53448   * @return {number} Greater than zero if the resolution.width attribute of
53449   * left is greater than the corresponding attribute of right. Less
53450   * than zero if the resolution.width of right is greater than left and
53451   * exactly zero if the two are equal.
53452   */
53453
53454
53455  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
53456    var leftWidth;
53457    var rightWidth;
53458
53459    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
53460      leftWidth = left.attributes.RESOLUTION.width;
53461    }
53462
53463    leftWidth = leftWidth || window_1.Number.MAX_VALUE;
53464
53465    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
53466      rightWidth = right.attributes.RESOLUTION.width;
53467    }
53468
53469    rightWidth = rightWidth || window_1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
53470    // have the same media dimensions/ resolution
53471
53472    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
53473      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
53474    }
53475
53476    return leftWidth - rightWidth;
53477  };
53478  /**
53479   * Chooses the appropriate media playlist based on bandwidth and player size
53480   *
53481   * @param {Object} master
53482   *        Object representation of the master manifest
53483   * @param {number} playerBandwidth
53484   *        Current calculated bandwidth of the player
53485   * @param {number} playerWidth
53486   *        Current width of the player element (should account for the device pixel ratio)
53487   * @param {number} playerHeight
53488   *        Current height of the player element (should account for the device pixel ratio)
53489   * @param {boolean} limitRenditionByPlayerDimensions
53490   *        True if the player width and height should be used during the selection, false otherwise
53491   * @param {Object} masterPlaylistController
53492   *        the current masterPlaylistController object
53493   * @return {Playlist} the highest bitrate playlist less than the
53494   * currently detected bandwidth, accounting for some amount of
53495   * bandwidth variance
53496   */
53497
53498
53499  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, masterPlaylistController) {
53500    // If we end up getting called before `master` is available, exit early
53501    if (!master) {
53502      return;
53503    }
53504
53505    var options = {
53506      bandwidth: playerBandwidth,
53507      width: playerWidth,
53508      height: playerHeight,
53509      limitRenditionByPlayerDimensions: limitRenditionByPlayerDimensions
53510    };
53511    var playlists = master.playlists; // if playlist is audio only, select between currently active audio group playlists.
53512
53513    if (Playlist.isAudioOnly(master)) {
53514      playlists = masterPlaylistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
53515      // at the buttom of this function for debugging.
53516
53517      options.audioOnly = true;
53518    } // convert the playlists to an intermediary representation to make comparisons easier
53519
53520
53521    var sortedPlaylistReps = playlists.map(function (playlist) {
53522      var bandwidth;
53523      var width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
53524      var height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
53525      bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
53526      bandwidth = bandwidth || window_1.Number.MAX_VALUE;
53527      return {
53528        bandwidth: bandwidth,
53529        width: width,
53530        height: height,
53531        playlist: playlist
53532      };
53533    });
53534    stableSort(sortedPlaylistReps, function (left, right) {
53535      return left.bandwidth - right.bandwidth;
53536    }); // filter out any playlists that have been excluded due to
53537    // incompatible configurations
53538
53539    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
53540      return !Playlist.isIncompatible(rep.playlist);
53541    }); // filter out any playlists that have been disabled manually through the representations
53542    // api or blacklisted temporarily due to playback errors.
53543
53544    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
53545      return Playlist.isEnabled(rep.playlist);
53546    });
53547
53548    if (!enabledPlaylistReps.length) {
53549      // if there are no enabled playlists, then they have all been blacklisted or disabled
53550      // by the user through the representations api. In this case, ignore blacklisting and
53551      // fallback to what the user wants by using playlists the user has not disabled.
53552      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
53553        return !Playlist.isDisabled(rep.playlist);
53554      });
53555    } // filter out any variant that has greater effective bitrate
53556    // than the current estimated bandwidth
53557
53558
53559    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
53560      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
53561    });
53562    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
53563    // and then taking the very first element
53564
53565    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
53566      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
53567    })[0]; // if we're not going to limit renditions by player size, make an early decision.
53568
53569    if (limitRenditionByPlayerDimensions === false) {
53570      var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53571
53572      if (_chosenRep && _chosenRep.playlist) {
53573        var type = 'sortedPlaylistReps';
53574
53575        if (bandwidthBestRep) {
53576          type = 'bandwidthBestRep';
53577        }
53578
53579        if (enabledPlaylistReps[0]) {
53580          type = 'enabledPlaylistReps';
53581        }
53582
53583        logFn("choosing " + representationToString(_chosenRep) + " using " + type + " with options", options);
53584        return _chosenRep.playlist;
53585      }
53586
53587      logFn('could not choose a playlist with options', options);
53588      return null;
53589    } // filter out playlists without resolution information
53590
53591
53592    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
53593      return rep.width && rep.height;
53594    }); // sort variants by resolution
53595
53596    stableSort(haveResolution, function (left, right) {
53597      return left.width - right.width;
53598    }); // if we have the exact resolution as the player use it
53599
53600    var resolutionBestRepList = haveResolution.filter(function (rep) {
53601      return rep.width === playerWidth && rep.height === playerHe
53601ight;
53602    });
53603    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
53604
53605    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
53606      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
53607    })[0];
53608    var resolutionPlusOneList;
53609    var resolutionPlusOneSmallest;
53610    var resolutionPlusOneRep; // find the smallest variant that is larger than the player
53611    // if there is no match of exact resolution
53612
53613    if (!resolutionBestRep) {
53614      resolutionPlusOneList = haveResolution.filter(function (rep) {
53615        return rep.width > playerWidth || rep.height > playerHeight;
53616      }); // find all the variants have the same smallest resolution
53617
53618      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
53619        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
53620      }); // ensure that we also pick the highest bandwidth variant that
53621      // is just-larger-than the video player
53622
53623      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
53624      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
53625        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
53626      })[0];
53627    }
53628
53629    var leastPixelDiffRep; // If this selector proves to be better than others,
53630    // resolutionPlusOneRep and resolutionBestRep and all
53631    // the code involving them should be removed.
53632
53633    if (masterPlaylistController.experimentalLeastPixelDiffSelector) {
53634      // find the variant that is closest to the player's pixel size
53635      var leastPixelDiffList = haveResolution.map(function (rep) {
53636        rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
53637        return rep;
53638      }); // get the highest bandwidth, closest resolution playlist
53639
53640      stableSort(leastPixelDiffList, function (left, right) {
53641        // sort by highest bandwidth if pixelDiff is the same
53642        if (left.pixelDiff === right.pixelDiff) {
53643          return right.bandwidth - left.bandwidth;
53644        }
53645
53646        return left.pixelDiff - right.pixelDiff;
53647      });
53648      leastPixelDiffRep = leastPixelDiffList[0];
53649    } // fallback chain of variants
53650
53651
53652    var chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53653
53654    if (chosenRep && chosenRep.playlist) {
53655      var _type = 'sortedPlaylistReps';
53656
53657      if (leastPixelDiffRep) {
53658        _type = 'leastPixelDiffRep';
53659      } else if (resolutionPlusOneRep) {
53660        _type = 'resolutionPlusOneRep';
53661      } else if (resolutionBestRep) {
53662        _type = 'resolutionBestRep';
53663      } else if (bandwidthBestRep) {
53664        _type = 'bandwidthBestRep';
53665      } else if (enabledPlaylistReps[0]) {
53666        _type = 'enabledPlaylistReps';
53667      }
53668
53669      logFn("choosing " + representationToString(chosenRep) + " using " + _type + " with options", options);
53670      return chosenRep.playlist;
53671    }
53672
53673    logFn('could not choose a playlist with options', options);
53674    return null;
53675  };
53676  /**
53677   * Chooses the appropriate media playlist based on the most recent
53678   * bandwidth estimate and the player size.
53679   *
53680   * Expects to be called within the context of an instance of VhsHandler
53681   *
53682   * @return {Playlist} the highest bitrate playlist less than the
53683   * currently detected bandwidth, accounting for some amount of
53684   * bandwidth variance
53685   */
53686
53687
53688  var lastBandwidthSelector = function lastBandwidthSelector() {
53689    var pixelRatio = this.useDevicePixelRatio ? window_1.devicePixelRatio || 1 : 1;
53690    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, this.masterPlaylistController_);
53691  };
53692  /**
53693   * Chooses the appropriate media playlist based on an
53694   * exponential-weighted moving average of the bandwidth after
53695   * filtering for player size.
53696   *
53697   * Expects to be called within the context of an instance of VhsHandler
53698   *
53699   * @param {number} decay - a number between 0 and 1. Higher values of
53700   * this parameter will cause previous bandwidth estimates to lose
53701   * significance more quickly.
53702   * @return {Function} a function which can be invoked to create a new
53703   * playlist selector function.
53704   * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
53705   */
53706
53707
53708  var movingAverageBandwidthSelector = function movingAverageBandwidthSelector(decay) {
53709    var average = -1;
53710    var lastSystemBandwidth = -1;
53711
53712    if (decay < 0 || decay > 1) {
53713      throw new Error('Moving average bandwidth decay must be between 0 and 1.');
53714    }
53715
53716    return function () {
53717      var pixelRatio = this.useDevicePixelRatio ? window_1.devicePixelRatio || 1 : 1;
53718
53719      if (average < 0) {
53720        average = this.systemBandwidth;
53721        lastSystemBandwidth = this.systemBandwidth;
53722      } // stop the average value from decaying for every 250ms
53723      // when the systemBandwidth is constant
53724      // and
53725      // stop average from setting to a very low value when the
53726      // systemBandwidth becomes 0 in case of chunk cancellation
53727
53728
53729      if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
53730        average = decay * this.systemBandwidth + (1 - decay) * average;
53731        lastSystemBandwidth = this.systemBandwidth;
53732      }
53733
53734      return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.masterPlaylistController_);
53735    };
53736  };
53737  /**
53738   * Chooses the appropriate media playlist based on the potential to rebuffer
53739   *
53740   * @param {Object} settings
53741   *        Object of information required to use this selector
53742   * @param {Object} settings.master
53743   *        Object representation of the master manifest
53744   * @param {number} settings.currentTime
53745   *        The current time of the player
53746   * @param {number} settings.bandwidth
53747   *        Current measured bandwidth
53748   * @param {number} settings.duration
53749   *        Duration of the media
53750   * @param {number} settings.segmentDuration
53751   *        Segment duration to be used in round trip time calculations
53752   * @param {number} settings.timeUntilRebuffer
53753   *        Time left in seconds until the player has to rebuffer
53754   * @param {number} settings.currentTimeline
53755   *        The current timeline segments are being loaded from
53756   * @param {SyncController} settings.syncController
53757   *        SyncController for determining if we have a sync point for a given playlist
53758   * @return {Object|null}
53759   *         {Object} return.playlist
53760   *         The highest bandwidth playlist with the least amount of rebuffering
53761   *         {Number} return.rebufferingImpact
53762   *         The amount of time in seconds switching to this playlist will rebuffer. A
53763   *         negative value means that switching will cause zero rebuffering.
53764   */
53765
53766
53767  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
53768    var master = settings.master,
53769        currentTime = settings.currentTime,
53770        bandwidth = settings.bandwidth,
53771        duration = settings.duration,
53772        segmentDuration = settings.segmentDuration,
53773        timeUntilRebuffer = settings.timeUntilRebuffer,
53774        currentTimeline = settings.currentTimeline,
53775        syncController = settings.syncController; // filter out any playlists that have been excluded due to
53776    // incompatible configurations
53777
53778    var compatiblePlaylists = master.playlists.filter(function (playlist) {
53779      return !Playlist.isIncompatible(playlist);
53780    }); // filter out any playlists that have been disabled manually through the representations
53781    // api or blacklisted temporarily due to playback errors.
53782
53783    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
53784
53785    if (!enabledPlaylists.length) {
53786      // if there are no enabled playlists, then they have all been blacklisted or disabled
53787      // by the user through the representations api. In this case, ignore blacklisting and
53788      // fallback to what the user wants by using playlists the user has not disabled.
53789      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
53790        return !Playlist.isDisabled(playlist);
53791      });
53792    }
53793
53794    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
53795    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
53796      var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
53797      // sync request first. This will double the request time
53798
53799      var numRequests = syncPoint ? 1 : 2;
53800      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
53801      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
53802      return {
53803        playlist: playlist,
53804        rebufferingImpact: rebufferingImpact
53805      };
53806    });
53807    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
53808      return estimate.rebufferingImpact <= 0;
53809    }); // Sort by bandwidth DESC
53810
53811    stableSort(noRebufferingPlaylists, function (a, b) {
53812      return comparePlaylistBandwidth(b.playlist, a.playlist);
53813    });
53814
53815    if (noRebufferingPlaylists.length) {
53816      return noRebufferingPlaylists[0];
53817    }
53818
53819    stableSort(rebufferingEstimates, function (a, b) {
53820      return a.rebufferingImpact - b.rebufferingImpact;
53821    });
53822    return rebufferingEstimates[0] || null;
53823  };
53824  /**
53825   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
53826   * one with video.  If no renditions with video exist, return the lowest audio rendition.
53827   *
53828   * Expects to be called within the context of an instance of VhsHandler
53829   *
53830   * @return {Object|null}
53831   *         {Object} return.playlist
53832   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
53833   *         exists pick the lowest audio rendition.
53834   */
53835
53836
53837  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
53838    var _this = this; // filter out any playlists that have been excluded due to
53839    // incompatible configurations or playback errors
53840
53841
53842    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
53843
53844    stableSort(playlists, function (a, b) {
53845      return comparePlaylistBandwidth(a, b);
53846    }); // Parse and assume that playlists with no video codec have no video
53847    // (this is not necessarily true, although it is generally true).
53848    //
53849    // If an entire manifest has no valid videos everything will get filtered
53850    // out.
53851
53852    var playlistsWithVideo = playlists.filter(function (playlist) {
53853      return !!codecsForPlaylist(_this.playlists.master, playlist).video;
53854    });
53855    return playlistsWithVideo[0] || null;
53856  };
53857  /**
53858   * Combine all segments into a single Uint8Array
53859   *
53860   * @param {Object} segmentObj
53861   * @return {Uint8Array} concatenated bytes
53862   * @private
53863   */
53864
53865
53866  var concatSegments = function concatSegments(segmentObj) {
53867    var offset = 0;
53868    var tempBuffer;
53869
53870    if (segmentObj.bytes) {
53871      tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
53872
53873      segmentObj.segments.forEach(function (segment) {
53874        tempBuffer.set(segment, offset);
53875        offset += segment.byteLength;
53876      });
53877    }
53878
53879    return tempBuffer;
53880  };
53881  /**
53882   * @file text-tracks.js
53883   */
53884
53885  /**
53886   * Create captions text tracks on video.js if they do not exist
53887   *
53888   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
53889   * @param {Object} tech the video.js tech
53890   * @param {Object} captionStream the caption stream to create
53891   * @private
53892   */
53893
53894
53895  var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStream) {
53896    if (!inbandTextTracks[captionStream]) {
53897      tech.trigger({
53898        type: 'usage',
53899        name: 'vhs-608'
53900      });
53901      tech.trigger({
53902        type: 'usage',
53903        name: 'hls-608'
53904      });
53905      var instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
53906
53907      if (/^cc708_/.test(captionStream)) {
53908        instreamId = 'SERVICE' + captionStream.split('_')[1];
53909      }
53910
53911      var track = tech.textTracks().getTrackById(instreamId);
53912
53913      if (track) {
53914        // Resuse an existing track with a CC# id because this was
53915        // very likely created by videojs-contrib-hls from information
53916        // in the m3u8 for us to use
53917        inbandTextTracks[captionStream] = track;
53918      } else {
53919        // This section gets called when we have caption services that aren't specified in the manifest.
53920        // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
53921        var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
53922        var label = captionStream;
53923        var language = captionStream;
53924        var def = false;
53925        var captionService = captionServices[instreamId];
53926
53927        if (captionService) {
53928          label = captionService.label;
53929          language = captionService.language;
53930          def = captionService["default"];
53931        } // Otherwise, create a track with the default `CC#` label and
53932        // without a language
53933
53934
53935        inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
53936          kind: 'captions',
53937          id: instreamId,
53938          // TODO: investigate why this doesn't seem to turn the caption on by default
53939          "default": def,
53940          label: label,
53941          language: language
53942        }, false).track;
53943      }
53944    }
53945  };
53946  /**
53947   * Add caption text track data to a source handler given an array of captions
53948   *
53949   * @param {Object}
53950   *   @param {Object} inbandTextTracks the inband text tracks
53951   *   @param {number} timestampOffset the timestamp offset of the source buffer
53952   *   @param {Array} captionArray an array of caption data
53953   * @private
53954   */
53955
53956
53957  var addCaptionData = function addCaptionData(_ref) {
53958    var inbandTextTracks = _ref.inbandTextTracks,
53959        captionArray = _ref.captionArray,
53960        timestampOffset = _ref.timestampOffset;
53961
53962    if (!captionArray) {
53963      return;
53964    }
53965
53966    var Cue = window_1.WebKitDataCue || window_1.VTTCue;
53967    captionArray.forEach(function (caption) {
53968      var track = caption.stream;
53969      inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
53970    });
53971  };
53972  /**
53973   * Define properties on a cue for backwards compatability,
53974   * but warn the user that the way that they are using it
53975   * is depricated and will be removed at a later date.
53976   *
53977   * @param {Cue} cue the cue to add the properties on
53978   * @private
53979   */
53980
53981
53982  var deprecateOldCue = function deprecateOldCue(cue) {
53983    Object.defineProperties(cue.frame, {
53984      id: {
53985        get: function get() {
53986          videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
53987          return cue.value.key;
53988        }
53989      },
53990      value: {
53991        get: function get() {
53992          videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
53993          return cue.value.data;
53994        }
53995      },
53996      privateData: {
53997        get: function get() {
53998          videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
53999          return cue.value.data;
54000        }
54001      }
54002    });
54003  };
54004  /**
54005   * Add metadata text track data to a source handler given an array of metadata
54006   *
54007   * @param {Object}
54008   *   @param {Object} inbandTextTracks the inband text tracks
54009   *   @param {Array} metadataArray an array of meta data
54010   *   @param {number} timestampOffset the timestamp offset of the source buffer
54011   *   @param {number} videoDuration the duration of the video
54012   * @private
54013   */
54014
54015
54016  var addMetadata = function addMetadata(_ref2) {
54017    var inbandTextTracks = _ref2.inbandTextTracks,
54018        metadataArray = _ref2.metadataArray,
54019        timestampOffset = _ref2.timestampOffset,
54020        videoDuration = _ref2.videoDuration;
54021
54022    if (!metadataArray) {
54023      return;
54024    }
54025
54026    var Cue = window_1.WebKitDataCue || window_1.VTTCue;
54027    var metadataTrack = inbandTextTracks.metadataTrack_;
54028
54029    if (!metadataTrack) {
54030      return;
54031    }
54032
54033    metadataArray.forEach(function (metadata) {
54034      var time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
54035      // ignore this bit of metadata.
54036      // This likely occurs when you have an non-timed ID3 tag like TIT2,
54037      // which is the "Title/Songname/Content description" frame
54038
54039      if (typeof time !== 'number' || window_1.isNaN(time) || time < 0 || !(time < Infinity)) {
54040        return;
54041      }
54042
54043      metadata.frames.forEach(function (frame) {
54044        var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
54045        cue.frame = frame;
54046        cue.value = frame;
54047        deprecateOldCue(cue);
54048        metadataTrack.addCue(cue);
54049      });
54050    });
54051
54052    if (!metadataTrack.cues || !metadataTrack.cues.length) {
54053      return;
54054    } // Updating the metadeta cues so that
54055    // the endTime of each cue is the startTime of the next cue
54056    // the endTime of last cue is the duration of the video
54057
54058
54059    var cues = metadataTrack.cues;
54060    var cuesArray = []; // Create a copy of the TextTrackCueList...
54061    // ...disregarding cues with a falsey value
54062
54063    for (var i = 0; i < cues.length; i++) {
54064      if (cues[i]) {
54065        cuesArray.push(cues[i]);
54066      }
54067    } // Group cues by their startTime value
54068
54069
54070    var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
54071      var timeSlot = obj[cue.startTime] || [];
54072      timeSlot.push(cue);
54073      obj[cue.startTime] = timeSlot;
54074      return obj;
54075    }, {}); // Sort startTimes by ascending order
54076
54077    var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
54078      return Number(a) - Number(b);
54079    }); // Map each cue group's endTime to the next group's startTime
54080
54081    sortedStartTimes.forEach(function (startTime, idx) {
54082      var cueGroup = cuesGroupedByStartTime[startTime];
54083      var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
54084
54085      cueGroup.forEach(function (cue) {
54086        cue.endTime = nextTime;
54087      });
54088    });
54089  };
54090  /**
54091   * Create metadata text track on video.js if it does not exist
54092   *
54093   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
54094   * @param {string} dispatchType the inband metadata track dispatch type
54095   * @param {Object} tech the video.js tech
54096   * @private
54097   */
54098
54099
54100  var createMetadataTrackIfNotExists = function createMetadataTrackIfNotExists(inbandTextTracks, dispatchType, tech) {
54101    if (inbandTextTracks.metadataTrack_) {
54102      return;
54103    }
54104
54105    inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
54106      kind: 'metadata',
54107      label: 'Timed Metadata'
54108    }, false).track;
54109    inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
54110  };
54111  /**
54112   * Remove cues from a track on video.js.
54113   *
54114   * @param {Double} start start of where we should remove the cue
54115   * @param {Double} end end of where the we should remove the cue
54116   * @param {Object} track the text track to remove the cues from
54117   * @private
54118   */
54119
54120
54121  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
54122    var i;
54123    var cue;
54124
54125    if (!track) {
54126      return;
54127    }
54128
54129    if (!track.cues) {
54130      return;
54131    }
54132
54133    i = track.cues.length;
54134
54135    while (i--) {
54136      cue = track.cues[i]; // Remove any cue within the provided start and end time
54137
54138      if (cue.startTime >= start && cue.endTime <= end) {
54139        track.removeCue(cue);
54140      }
54141    }
54142  };
54143  /**
54144   * Remove duplicate cues from a track on video.js (a cue is considered a
54145   * duplicate if it has the same time interval and text as another)
54146   *
54147   * @param {Object} track the text track to remove the duplicate cues from
54148   * @private
54149   */
54150
54151
54152  var removeDuplicateCuesFromTrack = function removeDuplicateCuesFromTrack(track) {
54153    var cues = track.cues;
54154
54155    if (!cues) {
54156      return;
54157    }
54158
54159    for (var i = 0; i < cues.length; i++) {
54160      var duplicates = [];
54161      var occurrences = 0;
54162
54163      for (var j = 0; j < cues.length; j++) {
54164        if (cues[i].startTime === cues[j].startTime && cues[i].endTime === cues[j].endTime && cues[i].text === cues[j].text) {
54165          occurrences++;
54166
54167          if (occurrences > 1) {
54168            duplicates.push(cues[j]);
54169          }
54170        }
54171      }
54172
54173      if (duplicates.length) {
54174        duplicates.forEach(function (dupe) {
54175          return track.removeCue(dupe);
54176        });
54177      }
54178    }
54179  };
54180  /**
54181   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
54182   * front of current time.
54183   *
54184   * @param {Array} buffer
54185   *        The current buffer of gop information
54186   * @param {number} currentTime
54187   *        The current time
54188   * @param {Double} mapping
54189   *        Offset to map display time to stream presentation time
54190   * @return {Array}
54191   *         List of gops considered safe to append over
54192   */
54193
54194
54195  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
54196    if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
54197      return [];
54198    } // pts value for current time + 3 seconds to give a bit more wiggle room
54199
54200
54201    var currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1);
54202    var i;
54203
54204    for (i = 0; i < buffer.length; i++) {
54205      if (buffer[i].pts > currentTimePts) {
54206        break;
54207      }
54208    }
54209
54210    return buffer.slice(i);
54211  };
54212  /**
54213   * Appends gop information (timing and byteLength) received by the transmuxer for the
54214   * gops appended in the last call to appendBuffer
54215   *
54216   * @param {Array} buffer
54217   *        The current buffer of gop information
54218   * @param {Array} gops
54219   *        List of new gop information
54220   * @param {boolean} replace
54221   *        If true, replace the buffer with the new gop information. If false, append the
54222   *        new gop information to the buffer in the right location of time.
54223   * @return {Array}
54224   *         Updated list of gop information
54225   */
54226
54227
54228  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
54229    if (!gops.length) {
54230      return buffer;
54231    }
54232
54233    if (replace) {
54234      // If we are in safe append mode, then completely overwrite the gop buffer
54235      // with the most recent appeneded data. This will make sure that when appending
54236      // future segments, we only try to align with gops that are both ahead of current
54237      // time and in the last segment appended.
54238      return gops.slice();
54239    }
54240
54241    var start = gops[0].pts;
54242    var i = 0;
54243
54244    for (i; i < buffer.length; i++) {
54245      if (buffer[i].pts >= start) {
54246        break;
54247      }
54248    }
54249
54250    return buffer.slice(0, i).concat(gops);
54251  };
54252  /**
54253   * Removes gop information in buffer that overlaps with provided start and end
54254   *
54255   * @param {Array} buffer
54256   *        The current buffer of gop information
54257   * @param {Double} start
54258   *        position to start the remove at
54259   * @param {Double} end
54260   *        position to end the remove at
54261   * @param {Double} mapping
54262   *        Offset to map display time to stream presentation time
54263   */
54264
54265
54266  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
54267    var startPts = Math.ceil((start - mapping) * clock_1);
54268    var endPts = Math.ceil((end - mapping) * clock_1);
54269    var updatedBuffer = buffer.slice();
54270    var i = buffer.length;
54271
54272    while (i--) {
54273      if (buffer[i].pts <= endPts) {
54274        break;
54275      }
54276    }
54277
54278    if (i === -1) {
54279      // no removal because end of remove range is before start of buffer
54280      return updatedBuffer;
54281    }
54282
54283    var j = i + 1;
54284
54285    while (j--) {
54286      if (buffer[j].pts <= startPts) {
54287        break;
54288      }
54289    } // clamp remove range start to 0 index
54290
54291
54292    j = Math.max(j, 0);
54293    updatedBuffer.splice(j, i - j + 1);
54294    return updatedBuffer;
54295  };
54296
54297  var shallowEqual = function shallowEqual(a, b) {
54298    // if both are undefined
54299    // or one or the other is undefined
54300    // they are not equal
54301    if (!a && !b || !a && b || a && !b) {
54302      return false;
54303    } // they are the same object and thus, equal
54304
54305
54306    if (a === b) {
54307      return true;
54308    } // sort keys so we can make sure they have
54309    // all the same keys later.
54310
54311
54312    var akeys = Object.keys(a).sort();
54313    var bkeys = Object.keys(b).sort(); // different number of keys, not equal
54314
54315    if (akeys.length !== bkeys.length) {
54316      return false;
54317    }
54318
54319    for (var i = 0; i < akeys.length; i++) {
54320      var key = akeys[i]; // different sorted keys, not equal
54321
54322      if (key !== bkeys[i]) {
54323        return false;
54324      } // different values, not equal
54325
54326
54327      if (a[key] !== b[key]) {
54328        return false;
54329      }
54330    }
54331
54332    return true;
54333  }; // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
54334
54335
54336  var QUOTA_EXCEEDED_ERR = 22;
54337  /**
54338   * The segment loader has no recourse except to fetch a segment in the
54339   * current playlist and use the internal timestamps in that segment to
54340   * generate a syncPoint. This function returns a good candidate index
54341   * for that process.
54342   *
54343   * @param {Array} segments - the segments array from a playlist.
54344   * @return {number} An index of a segment from the playlist to load
54345   */
54346
54347  var getSyncSegmentCandidate = function getSyncSegmentCandidate(currentTimeline, segments, targetTime) {
54348    segments = segments || [];
54349    var timelineSegments = [];
54350    var time = 0;
54351
54352    for (var i = 0; i < segments.length; i++) {
54353      var segment = segments[i];
54354
54355      if (currentTimeline === segment.timeline) {
54356        timelineSegments.push(i);
54357        time += segment.duration;
54358
54359        if (time > targetTime) {
54360          return i;
54361        }
54362      }
54363    }
54364
54365    if (timelineSegments.length === 0) {
54366      return 0;
54367    } // default to the last timeline segment
54368
54369
54370    return timelineSegments[timelineSegments.length - 1];
54371  }; // In the event of a quota exceeded error, keep at least one second of back buffer. This
54372  // number was arbitrarily chosen and may be updated in the future, but seemed reasonable
54373  // as a start to prevent any potential issues with removing content too close to the
54374  // playhead.
54375
54376
54377  var MIN_BACK_BUFFER = 1; // in ms
54378
54379  var CHECK_BUFFER_DELAY = 500;
54380
54381  var finite = function finite(num) {
54382    return typeof num === 'number' && isFinite(num);
54383  }; // With most content hovering around 30fps, if a segment has a duration less than a half
54384  // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
54385  // not accurately reflect the rest of the content.
54386
54387
54388  var MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
54389
54390  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, trackInfo) {
54391    // Although these checks should most likely cover non 'main' types, for now it narrows
54392    // the scope of our checks.
54393    if (loaderType !== 'main' || !startingMedia || !trackInfo) {
54394      return null;
54395    }
54396
54397    if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
54398      return 'Neither audio nor video found in segment.';
54399    }
54400
54401    if (startingMedia.hasVideo && !trackInfo.hasVideo) {
54402      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.';
54403    }
54404
54405    if (!startingMedia.hasVideo && trackInfo.hasVideo) {
54406      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.';
54407    }
54408
54409    return null;
54410  };
54411  /**
54412   * Calculates a time value that is safe to remove from the back buffer without interrupting
54413   * playback.
54414   *
54415   * @param {TimeRange} seekable
54416   *        The current seekable range
54417   * @param {number} currentTime
54418   *        The current time of the player
54419   * @param {number} targetDuration
54420   *        The target duration of the current playlist
54421   * @return {number}
54422   *         Time that is safe to remove from the back buffer without interrupting playback
54423   */
54424
54425
54426  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) {
54427    // 30 seconds before the playhead provides a safe default for trimming.
54428    //
54429    // Choosing a reasonable default is particularly important for high bitrate content and
54430    // VOD videos/live streams with large windows, as the buffer may end up overfilled and
54431    // throw an APPEND_BUFFER_ERR.
54432    var trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
54433
54434    if (seekable.length) {
54435      // Some live playlists may have a shorter window of content than the full allowed back
54436      // buffer. For these playlists, don't save content that's no longer within the window.
54437      trimTime = Math.max(trimTime, seekable.start(0));
54438    } // Don't remove within target duration of the current time to avoid the possibility of
54439    // removing the GOP currently being played, as removing it can cause playback stalls.
54440
54441
54442    var maxTrimTime = currentTime - targetDuration;
54443    return Math.min(maxTrimTime, trimTime);
54444  };
54445
54446  var segmentInfoString = function segmentInfoString(segmentInfo) {
54447    var startOfSegment = segmentInfo.startOfSegment,
54448        duration = segmentInfo.duration,
54449        segment = segmentInfo.segment,
54450        part = segmentInfo.part,
54451        _segmentInfo$playlist = segmentInfo.playlist,
54452        seq = _segmentInfo$playlist.mediaSequence,
54453        id = _segmentInfo$playlist.id,
54454        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
54455        segments = _segmentInfo$playlist2 === void 0 ? [] : _segmentInfo$playlist2,
54456        index = segmentInfo.mediaIndex,
54457        partIndex = segmentInfo.partIndex,
54458        timeline = segmentInfo.timeline;
54459    var segmentLen = segments.length - 1;
54460    var selection = 'mediaIndex/partIndex increment';
54461
54462    if (segmentInfo.getMediaInfoForTime) {
54463      selection = "getMediaInfoForTime (" + segmentInfo.getMediaInfoForTime + ")";
54464    } else if (segmentInfo.isSyncRequest) {
54465      selection = 'getSyncSegmentCandidate (isSyncRequest)';
54466    }
54467
54468    var hasPartIndex = typeof partIndex === 'number';
54469    var name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
54470    var zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
54471      preloadSegment: segment
54472    }) - 1 : 0;
54473    return name + " [" + (seq + index) + "/" + (seq + segmentLen) + "]" + (hasPartIndex ? " part [" + partIndex + "/" + zeroBasedPartCount + "]" : '') + (" segment start/end [" + segment.start + " => " + segment.end + "]") + (hasPartIndex ? " part start/end [" + part.start + " => " + part.end + "]" : '') + (" startOfSegment [" + startOfSegment + "]") + (" duration [" + duration + "]") + (" timeline [" + timeline + "]") + (" selected by [" + selection + "]") + (" playlist [" + id + "]");
54474  };
54475
54476  var timingInfoPropertyForMedia = function timingInfoPropertyForMedia(mediaType) {
54477    return mediaType + "TimingInfo";
54478  };
54479  /**
54480   * Returns the timestamp offset to use for the segment.
54481   *
54482   * @param {number} segmentTimeline
54483   *        The timeline of the segment
54484   * @param {number} currentTimeline
54485   *        The timeline currently being followed by the loader
54486   * @param {number} startOfSegment
54487   *        The estimated segment start
54488   * @param {TimeRange[]} buffered
54489   *        The loader's buffer
54490   * @param {boolean} overrideCheck
54491   *        If true, no checks are made to see if the timestamp offset value should be set,
54492   *        but sets it directly to a value.
54493   *
54494   * @return {number|null}
54495   *         Either a number representing a new timestamp offset, or null if the segment is
54496   *         part of the same timeline
54497   */
54498
54499
54500  var timestampOffsetForSegment = function timestampOffsetForSegment(_ref) {
54501    var segmentTimeline = _ref.segmentTimeline,
54502        currentTimeline = _ref.currentTimeline,
54503        startOfSegment = _ref.startOfSegment,
54504        buffered = _ref.buffered,
54505        overrideCheck = _ref.overrideCheck; // Check to see if we are crossing a discontinuity to see if we need to set the
54506    // timestamp offset on the transmuxer and source buffer.
54507    //
54508    // Previously, we changed the timestampOffset if the start of this segment was less than
54509    // the currently set timestampOffset, but this isn't desirable as it can produce bad
54510    // behavior, especially around long running live streams.
54511
54512    if (!overrideCheck && segmentTimeline === currentTimeline) {
54513      return null;
54514    } // When changing renditions, it's possible to request a segment on an older timeline. For
54515    // instance, given two renditions with the following:
54516    //
54517    // #EXTINF:10
54518    // segment1
54519    // #EXT-X-DISCONTINUITY
54520    // #EXTINF:10
54521    // segment2
54522    // #EXTINF:10
54523    // segment3
54524    //
54525    // And the current player state:
54526    //
54527    // current time: 8
54528    // buffer: 0 => 20
54529    //
54530    // The next segment on the current rendition would be segment3, filling the buffer from
54531    // 20s onwards. However, if a rendition switch happens after segment2 was requested,
54532    // then the next segment to be requested will be segment1 from the new rendition in
54533    // order to fill time 8 and onwards. Using the buffered end would result in repeated
54534    // content (since it would position segment1 of the new rendition starting at 20s). This
54535    // case can be identified when the new segment's timeline is a prior value. Instead of
54536    // using the buffered end, the startOfSegment can be used, which, hopefully, will be
54537    // more accurate to the actual start time of the segment.
54538
54539
54540    if (segmentTimeline < currentTimeline) {
54541      return startOfSegment;
54542    } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
54543    // value uses the end of the last segment if it is available. While this value
54544    // should often be correct, it's better to rely on the buffered end, as the new
54545    // content post discontinuity should line up with the buffered end as if it were
54546    // time 0 for the new content.
54547
54548
54549    return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
54550  };
54551  /**
54552   * Returns whether or not the loader should wait for a timeline change from the timeline
54553   * change controller before processing the segment.
54554   *
54555   * Primary timing in VHS goes by video. This is different from most media players, as
54556   * audio is more often used as the primary timing source. For the foreseeable future, VHS
54557   * will continue to use video as the primary timing source, due to the current logic and
54558   * expectations built around it.
54559
54560   * Since the timing follows video, in order to maintain sync, the video loader is
54561   * responsible for setting both audio and video source buffer timestamp offsets.
54562   *
54563   * Setting different values for audio and video source buffers could lead to
54564   * desyncing. The following examples demonstrate some of the situations where this
54565   * distinction is important. Note that all of these cases involve demuxed content. When
54566   * content is muxed, the audio and video are packaged together, therefore syncing
54567   * separate media playlists is not an issue.
54568   *
54569   * CASE 1: Audio prepares to load a new timeline before video:
54570   *
54571   * Timeline:       0                 1
54572   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54573   * Audio Loader:                     ^
54574   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54575   * Video Loader              ^
54576   *
54577   * In the above example, the audio loader is preparing to load the 6th segment, the first
54578   * after a discontinuity, while the video loader is still loading the 5th segment, before
54579   * the discontinuity.
54580   *
54581   * If the audio loader goes ahead and loads and appends the 6th segment before the video
54582   * loader crosses the discontinuity, then when appended, the 6th audio segment will use
54583   * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
54584   * the audio loader must provide the audioAppendStart value to trim the content in the
54585   * transmuxer, and that value relies on the audio timestamp offset. Since the audio
54586   * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
54587   * segment until that value is provided.
54588   *
54589   * CASE 2: Video prepares to load a new timeline before audio:
54590   *
54591   * Timeline:       0                 1
54592   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54593   * Audio Loader:             ^
54594   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54595   * Video Loader                      ^
54596   *
54597   * In the above example, the video loader is preparing to load the 6th segment, the first
54598   * after a discontinuity, while the audio loader is still loading the 5th segment, before
54599   * the discontinuity.
54600   *
54601   * If the video loader goes ahead and loads and appends the 6th segment, then once the
54602   * segment is loaded and processed, both the video and audio timestamp offsets will be
54603   * set, since video is used as the primary timing source. This is to ensure content lines
54604   * up appropriately, as any modifications to the video timing are reflected by audio when
54605   * the video loader sets the audio and video timestamp offsets to the same value. However,
54606   * setting the timestamp offset for audio before audio has had a chance to change
54607   * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
54608   * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
54609   *
54610   * CASE 3: When seeking, audio prepares to load a new timeline before video
54611   *
54612   * Timeline:       0                 1
54613   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54614   * Audio Loader:           ^
54615   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54616   * Video Loader            ^
54617   *
54618   * In the above example, both audio and video loaders are loading segments from timeline
54619   * 0, but imagine that the seek originated from timeline 1.
54620   *
54621   * When seeking to a new timeline, the timestamp offset will be set based on the expected
54622   * segment start of the loaded video segment. In order to maintain sync, the audio loader
54623   * must wait for the video loader to load its segment and update both the audio and video
54624   * timestamp offsets before it may load and append its own segment. This is the case
54625   * whether the seek results in a mismatched segment request (e.g., the audio loader
54626   * chooses to load segment 3 and the video loader chooses to load segment 4) or the
54627   * loaders choose to load the same segment index from each playlist, as the segments may
54628   * not be aligned perfectly, even for matching segment indexes.
54629   *
54630   * @param {Object} timelinechangeController
54631   * @param {number} currentTimeline
54632   *        The timeline currently being followed by the loader
54633   * @param {number} segmentTimeline
54634   *        The timeline of the segment being loaded
54635   * @param {('main'|'audio')} loaderType
54636   *        The loader type
54637   * @param {boolean} audioDisabled
54638   *        Whether the audio is disabled for the loader. This should only be true when the
54639   *        loader may have muxed audio in its segment, but should not append it, e.g., for
54640   *        the main loader when an alternate audio playlist is active.
54641   *
54642   * @return {boolean}
54643   *         Whether the loader should wait for a timeline change from the timeline change
54644   *         controller before processing the segment
54645   */
54646
54647
54648  var shouldWaitForTimelineChange = function shouldWaitForTimelineChange(_ref2) {
54649    var timelineChangeController = _ref2.timelineChangeController,
54650        currentTimeline = _ref2.currentTimeline,
54651        segmentTimeline = _ref2.segmentTimeline,
54652        loaderType = _ref2.loaderType,
54653        audioDisabled = _ref2.audioDisabled;
54654
54655    if (currentTimeline === segmentTimeline) {
54656      return false;
54657    }
54658
54659    if (loaderType === 'audio') {
54660      var lastMainTimelineChange = timelineChangeController.lastTimelineChange({
54661        type: 'main'
54662      }); // Audio loader should wait if:
54663      //
54664      // * main hasn't had a timeline change yet (thus has not loaded its first segment)
54665      // * main hasn't yet changed to the timeline audio is looking to load
54666
54667      return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
54668    } // The main loader only needs to wait for timeline changes if there's demuxed audio.
54669    // Otherwise, there's nothing to wait for, since audio would be muxed into the main
54670    // loader's segments (or the content is audio/video only and handled by the main
54671    // loader).
54672
54673
54674    if (loaderType === 'main' && audioDisabled) {
54675      var pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
54676        type: 'audio'
54677      }); // Main loader should wait for the audio loader if audio is not pe
54677nding a timeline
54678      // change to the current timeline.
54679      //
54680      // Since the main loader is responsible for setting the timestamp offset for both
54681      // audio and video, the main loader must wait for audio to be about to change to its
54682      // timeline before setting the offset, otherwise, if audio is behind in loading,
54683      // segments from the previous timeline would be adjusted by the new timestamp offset.
54684      //
54685      // This requirement means that video will not cross a timeline until the audio is
54686      // about to cross to it, so that way audio and video will always cross the timeline
54687      // together.
54688      //
54689      // In addition to normal timeline changes, these rules also apply to the start of a
54690      // stream (going from a non-existent timeline, -1, to timeline 0). It's important
54691      // that these rules apply to the first timeline change because if they did not, it's
54692      // possible that the main loader will cross two timelines before the audio loader has
54693      // crossed one. Logic may be implemented to handle the startup as a special case, but
54694      // it's easier to simply treat all timeline changes the same.
54695
54696      if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
54697        return false;
54698      }
54699
54700      return true;
54701    }
54702
54703    return false;
54704  };
54705
54706  var mediaDuration = function mediaDuration(audioTimingInfo, videoTimingInfo) {
54707    var audioDuration = audioTimingInfo && typeof audioTimingInfo.start === 'number' && typeof audioTimingInfo.end === 'number' ? audioTimingInfo.end - audioTimingInfo.start : 0;
54708    var videoDuration = videoTimingInfo && typeof videoTimingInfo.start === 'number' && typeof videoTimingInfo.end === 'number' ? videoTimingInfo.end - videoTimingInfo.start : 0;
54709    return Math.max(audioDuration, videoDuration);
54710  };
54711
54712  var segmentTooLong = function segmentTooLong(_ref3) {
54713    var segmentDuration = _ref3.segmentDuration,
54714        maxDuration = _ref3.maxDuration; // 0 duration segments are most likely due to metadata only segments or a lack of
54715    // information.
54716
54717    if (!segmentDuration) {
54718      return false;
54719    } // For HLS:
54720    //
54721    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
54722    // The EXTINF duration of each Media Segment in the Playlist
54723    // file, when rounded to the nearest integer, MUST be less than or equal
54724    // to the target duration; longer segments can trigger playback stalls
54725    // or other errors.
54726    //
54727    // For DASH, the mpd-parser uses the largest reported segment duration as the target
54728    // duration. Although that reported duration is occasionally approximate (i.e., not
54729    // exact), a strict check may report that a segment is too long more often in DASH.
54730
54731
54732    return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
54733  };
54734
54735  var getTroublesomeSegmentDurationMessage = function getTroublesomeSegmentDurationMessage(segmentInfo, sourceType) {
54736    // Right now we aren't following DASH's timing model exactly, so only perform
54737    // this check for HLS content.
54738    if (sourceType !== 'hls') {
54739      return null;
54740    }
54741
54742    var segmentDuration = mediaDuration(segmentInfo.audioTimingInfo, segmentInfo.videoTimingInfo); // Don't report if we lack information.
54743    //
54744    // If the segment has a duration of 0 it is either a lack of information or a
54745    // metadata only segment and shouldn't be reported here.
54746
54747    if (!segmentDuration) {
54748      return null;
54749    }
54750
54751    var targetDuration = segmentInfo.playlist.targetDuration;
54752    var isSegmentWayTooLong = segmentTooLong({
54753      segmentDuration: segmentDuration,
54754      maxDuration: targetDuration * 2
54755    });
54756    var isSegmentSlightlyTooLong = segmentTooLong({
54757      segmentDuration: segmentDuration,
54758      maxDuration: targetDuration
54759    });
54760    var segmentTooLongMessage = "Segment with index " + segmentInfo.mediaIndex + " " + ("from playlist " + segmentInfo.playlist.id + " ") + ("has a duration of " + segmentDuration + " ") + ("when the reported duration is " + segmentInfo.duration + " ") + ("and the target duration is " + targetDuration + ". ") + 'For HLS content, a duration in excess of the target duration may result in ' + 'playback issues. See the HLS specification section on EXT-X-TARGETDURATION for ' + 'more details: ' + 'https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1';
54761
54762    if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
54763      return {
54764        severity: isSegmentWayTooLong ? 'warn' : 'info',
54765        message: segmentTooLongMessage
54766      };
54767    }
54768
54769    return null;
54770  };
54771  /**
54772   * An object that manages segment loading and appending.
54773   *
54774   * @class SegmentLoader
54775   * @param {Object} options required and optional options
54776   * @extends videojs.EventTarget
54777   */
54778
54779
54780  var SegmentLoader = /*#__PURE__*/function (_videojs$EventTarget) {
54781    inheritsLoose(SegmentLoader, _videojs$EventTarget);
54782
54783    function SegmentLoader(settings, options) {
54784      var _this;
54785
54786      _this = _videojs$EventTarget.call(this) || this; // check pre-conditions
54787
54788      if (!settings) {
54789        throw new TypeError('Initialization settings are required');
54790      }
54791
54792      if (typeof settings.currentTime !== 'function') {
54793        throw new TypeError('No currentTime getter specified');
54794      }
54795
54796      if (!settings.mediaSource) {
54797        throw new TypeError('No MediaSource specified');
54798      } // public properties
54799
54800
54801      _this.bandwidth = settings.bandwidth;
54802      _this.throughput = {
54803        rate: 0,
54804        count: 0
54805      };
54806      _this.roundTrip = NaN;
54807
54808      _this.resetStats_();
54809
54810      _this.mediaIndex = null;
54811      _this.partIndex = null; // private settings
54812
54813      _this.hasPlayed_ = settings.hasPlayed;
54814      _this.currentTime_ = settings.currentTime;
54815      _this.seekable_ = settings.seekable;
54816      _this.seeking_ = settings.seeking;
54817      _this.duration_ = settings.duration;
54818      _this.mediaSource_ = settings.mediaSource;
54819      _this.vhs_ = settings.vhs;
54820      _this.loaderType_ = settings.loaderType;
54821      _this.currentMediaInfo_ = void 0;
54822      _this.startingMediaInfo_ = void 0;
54823      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
54824      _this.goalBufferLength_ = settings.goalBufferLength;
54825      _this.sourceType_ = settings.sourceType;
54826      _this.sourceUpdater_ = settings.sourceUpdater;
54827      _this.inbandTextTracks_ = settings.inbandTextTracks;
54828      _this.state_ = 'INIT';
54829      _this.timelineChangeController_ = settings.timelineChangeController;
54830      _this.shouldSaveSegmentTimingInfo_ = true;
54831      _this.parse708captions_ = settings.parse708captions;
54832      _this.experimentalExactManifestTimings = settings.experimentalExactManifestTimings; // private instance variables
54833
54834      _this.checkBufferTimeout_ = null;
54835      _this.error_ = void 0;
54836      _this.currentTimeline_ = -1;
54837      _this.pendingSegment_ = null;
54838      _this.xhrOptions_ = null;
54839      _this.pendingSegments_ = [];
54840      _this.audioDisabled_ = false;
54841      _this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
54842
54843      _this.gopBuffer_ = [];
54844      _this.timeMapping_ = 0;
54845      _this.safeAppend_ = videojs.browser.IE_VERSION >= 11;
54846      _this.appendInitSegment_ = {
54847        audio: true,
54848        video: true
54849      };
54850      _this.playlistOfLastInitSegment_ = {
54851        audio: null,
54852        video: null
54853      };
54854      _this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
54855      // information yet to start the loading process (e.g., if the audio loader wants to
54856      // load a segment from the next timeline but the main loader hasn't yet crossed that
54857      // timeline), then the load call will be added to the queue until it is ready to be
54858      // processed.
54859
54860      _this.loadQueue_ = [];
54861      _this.metadataQueue_ = {
54862        id3: [],
54863        caption: []
54864      };
54865      _this.waitingOnRemove_ = false;
54866      _this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
54867
54868      _this.activeInitSegmentId_ = null;
54869      _this.initSegments_ = {}; // HLSe playback
54870
54871      _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
54872      _this.keyCache_ = {};
54873      _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
54874      // between a time in the display time and a segment index within
54875      // a playlist
54876
54877      _this.syncController_ = settings.syncController;
54878      _this.syncPoint_ = {
54879        segmentIndex: 0,
54880        time: 0
54881      };
54882      _this.transmuxer_ = _this.createTransmuxer_();
54883
54884      _this.triggerSyncInfoUpdate_ = function () {
54885        return _this.trigger('syncinfoupdate');
54886      };
54887
54888      _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_);
54889
54890      _this.mediaSource_.addEventListener('sourceopen', function () {
54891        if (!_this.isEndOfStream_()) {
54892          _this.ended_ = false;
54893        }
54894      }); // ...for determining the fetch location
54895
54896
54897      _this.fetchAtBuffer_ = false;
54898      _this.logger_ = logger("SegmentLoader[" + _this.loaderType_ + "]");
54899      Object.defineProperty(assertThisInitialized(_this), 'state', {
54900        get: function get() {
54901          return this.state_;
54902        },
54903        set: function set(newState) {
54904          if (newState !== this.state_) {
54905            this.logger_(this.state_ + " -> " + newState);
54906            this.state_ = newState;
54907            this.trigger('statechange');
54908          }
54909        }
54910      });
54911
54912      _this.sourceUpdater_.on('ready', function () {
54913        if (_this.hasEnoughInfoToAppend_()) {
54914          _this.processCallQueue_();
54915        }
54916      }); // Only the main loader needs to listen for pending timeline changes, as the main
54917      // loader should wait for audio to be ready to change its timeline so that both main
54918      // and audio timelines change together. For more details, see the
54919      // shouldWaitForTimelineChange function.
54920
54921
54922      if (_this.loaderType_ === 'main') {
54923        _this.timelineChangeController_.on('pendingtimelinechange', function () {
54924          if (_this.hasEnoughInfoToAppend_()) {
54925            _this.processCallQueue_();
54926          }
54927        });
54928      } // The main loader only listens on pending timeline changes, but the audio loader,
54929      // since its loads follow main, needs to listen on timeline changes. For more details,
54930      // see the shouldWaitForTimelineChange function.
54931
54932
54933      if (_this.loaderType_ === 'audio') {
54934        _this.timelineChangeController_.on('timelinechange', function () {
54935          if (_this.hasEnoughInfoToLoad_()) {
54936            _this.processLoadQueue_();
54937          }
54938
54939          if (_this.hasEnoughInfoToAppend_()) {
54940            _this.processCallQueue_();
54941          }
54942        });
54943      }
54944
54945      return _this;
54946    }
54947
54948    var _proto = SegmentLoader.prototype;
54949
54950    _proto.createTransmuxer_ = function createTransmuxer_() {
54951      return segmentTransmuxer.createTransmuxer({
54952        remux: false,
54953        alignGopsAtEnd: this.safeAppend_,
54954        keepOriginalTimestamps: true,
54955        parse708captions: this.parse708captions_
54956      });
54957    }
54958    /**
54959     * reset all of our media stats
54960     *
54961     * @private
54962     */
54963    ;
54964
54965    _proto.resetStats_ = function resetStats_() {
54966      this.mediaBytesTransferred = 0;
54967      this.mediaRequests = 0;
54968      this.mediaRequestsAborted = 0;
54969      this.mediaRequestsTimedout = 0;
54970      this.mediaRequestsErrored = 0;
54971      this.mediaTransferDuration = 0;
54972      this.mediaSecondsLoaded = 0;
54973      this.mediaAppends = 0;
54974    }
54975    /**
54976     * dispose of the SegmentLoader and reset to the default state
54977     */
54978    ;
54979
54980    _proto.dispose = function dispose() {
54981      this.trigger('dispose');
54982      this.state = 'DISPOSED';
54983      this.pause();
54984      this.abort_();
54985
54986      if (this.transmuxer_) {
54987        this.transmuxer_.terminate();
54988      }
54989
54990      this.resetStats_();
54991
54992      if (this.checkBufferTimeout_) {
54993        window_1.clearTimeout(this.checkBufferTimeout_);
54994      }
54995
54996      if (this.syncController_ && this.triggerSyncInfoUpdate_) {
54997        this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
54998      }
54999
55000      this.off();
55001    };
55002
55003    _proto.setAudio = function setAudio(enable) {
55004      this.audioDisabled_ = !enable;
55005
55006      if (enable) {
55007        this.appendInitSegment_.audio = true;
55008      } else {
55009        // remove current track audio if it gets disabled
55010        this.sourceUpdater_.removeAudio(0, this.duration_());
55011      }
55012    }
55013    /**
55014     * abort anything that is currently doing on with the SegmentLoader
55015     * and reset to a default state
55016     */
55017    ;
55018
55019    _proto.abort = function abort() {
55020      if (this.state !== 'WAITING') {
55021        if (this.pendingSegment_) {
55022          this.pendingSegment_ = null;
55023        }
55024
55025        return;
55026      }
55027
55028      this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
55029      // since we are no longer "waiting" on any requests. XHR callback is not always run
55030      // when the request is aborted. This will prevent the loader from being stuck in the
55031      // WAITING state indefinitely.
55032
55033      this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
55034      // next segment
55035
55036      if (!this.paused()) {
55037        this.monitorBuffer_();
55038      }
55039    }
55040    /**
55041     * abort all pending xhr requests and null any pending segements
55042     *
55043     * @private
55044     */
55045    ;
55046
55047    _proto.abort_ = function abort_() {
55048      if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
55049        this.pendingSegment_.abortRequests();
55050      } // clear out the segment being processed
55051
55052
55053      this.pendingSegment_ = null;
55054      this.callQueue_ = [];
55055      this.loadQueue_ = [];
55056      this.metadataQueue_.id3 = [];
55057      this.metadataQueue_.caption = [];
55058      this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
55059      this.waitingOnRemove_ = false;
55060      window_1.clearTimeout(this.quotaExceededErrorRetryTimeout_);
55061      this.quotaExceededErrorRetryTimeout_ = null;
55062    };
55063
55064    _proto.checkForAbort_ = function checkForAbort_(requestId) {
55065      // If the state is APPENDING, then aborts will not modify the state, meaning the first
55066      // callback that happens should reset the state to READY so that loading can continue.
55067      if (this.state === 'APPENDING' && !this.pendingSegment_) {
55068        this.state = 'READY';
55069        return true;
55070      }
55071
55072      if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
55073        return true;
55074      }
55075
55076      return false;
55077    }
55078    /**
55079     * set an error on the segment loader and null out any pending segements
55080     *
55081     * @param {Error} error the error to set on the SegmentLoader
55082     * @return {Error} the error that was set or that is currently set
55083     */
55084    ;
55085
55086    _proto.error = function error(_error) {
55087      if (typeof _error !== 'undefined') {
55088        this.logger_('error occurred:', _error);
55089        this.error_ = _error;
55090      }
55091
55092      this.pendingSegment_ = null;
55093      return this.error_;
55094    };
55095
55096    _proto.endOfStream = function endOfStream() {
55097      this.ended_ = true;
55098
55099      if (this.transmuxer_) {
55100        // need to clear out any cached data to prepare for the new segment
55101        segmentTransmuxer.reset(this.transmuxer_);
55102      }
55103
55104      this.gopBuffer_.length = 0;
55105      this.pause();
55106      this.trigger('ended');
55107    }
55108    /**
55109     * Indicates which time ranges are buffered
55110     *
55111     * @return {TimeRange}
55112     *         TimeRange object representing the current buffered ranges
55113     */
55114    ;
55115
55116    _proto.buffered_ = function buffered_() {
55117      var trackInfo = this.getMediaInfo_();
55118
55119      if (!this.sourceUpdater_ || !trackInfo) {
55120        return videojs.createTimeRanges();
55121      }
55122
55123      if (this.loaderType_ === 'main') {
55124        var hasAudio = trackInfo.hasAudio,
55125            hasVideo = trackInfo.hasVideo,
55126            isMuxed = trackInfo.isMuxed;
55127
55128        if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
55129          return this.sourceUpdater_.buffered();
55130        }
55131
55132        if (hasVideo) {
55133          return this.sourceUpdater_.videoBuffered();
55134        }
55135      } // One case that can be ignored for now is audio only with alt audio,
55136      // as we don't yet have proper support for that.
55137
55138
55139      return this.sourceUpdater_.audioBuffered();
55140    }
55141    /**
55142     * Gets and sets init segment for the provided map
55143     *
55144     * @param {Object} map
55145     *        The map object representing the init segment to get or set
55146     * @param {boolean=} set
55147     *        If true, the init segment for the provided map should be saved
55148     * @return {Object}
55149     *         map object for desired init segment
55150     */
55151    ;
55152
55153    _proto.initSegmentForMap = function initSegmentForMap(map, set) {
55154      if (set === void 0) {
55155        set = false;
55156      }
55157
55158      if (!map) {
55159        return null;
55160      }
55161
55162      var id = initSegmentId(map);
55163      var storedMap = this.initSegments_[id];
55164
55165      if (set && !storedMap && map.bytes) {
55166        this.initSegments_[id] = storedMap = {
55167          resolvedUri: map.resolvedUri,
55168          byterange: map.byterange,
55169          bytes: map.bytes,
55170          tracks: map.tracks,
55171          timescales: map.timescales
55172        };
55173      }
55174
55175      return storedMap || map;
55176    }
55177    /**
55178     * Gets and sets key for the provided key
55179     *
55180     * @param {Object} key
55181     *        The key object representing the key to get or set
55182     * @param {boolean=} set
55183     *        If true, the key for the provided key should be saved
55184     * @return {Object}
55185     *         Key object for desired key
55186     */
55187    ;
55188
55189    _proto.segmentKey = function segmentKey(key, set) {
55190      if (set === void 0) {
55191        set = false;
55192      }
55193
55194      if (!key) {
55195        return null;
55196      }
55197
55198      var id = segmentKeyId(key);
55199      var storedKey = this.keyCache_[id];
55199 // TODO: We should use the HTTP Expires header to invalidate our cache per
55200      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
55201
55202      if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
55203        this.keyCache_[id] = storedKey = {
55204          resolvedUri: key.resolvedUri,
55205          bytes: key.bytes
55206        };
55207      }
55208
55209      var result = {
55210        resolvedUri: (storedKey || key).resolvedUri
55211      };
55212
55213      if (storedKey) {
55214        result.bytes = storedKey.bytes;
55215      }
55216
55217      return result;
55218    }
55219    /**
55220     * Returns true if all configuration required for loading is present, otherwise false.
55221     *
55222     * @return {boolean} True if the all configuration is ready for loading
55223     * @private
55224     */
55225    ;
55226
55227    _proto.couldBeginLoading_ = function couldBeginLoading_() {
55228      return this.playlist_ && !this.paused();
55229    }
55230    /**
55231     * load a playlist and start to fill the buffer
55232     */
55233    ;
55234
55235    _proto.load = function load() {
55236      // un-pause
55237      this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
55238      // specified
55239
55240      if (!this.playlist_) {
55241        return;
55242      } // if all the configuration is ready, initialize and begin loading
55243
55244
55245      if (this.state === 'INIT' && this.couldBeginLoading_()) {
55246        return this.init_();
55247      } // if we're in the middle of processing a segment already, don't
55248      // kick off an additional segment request
55249
55250
55251      if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
55252        return;
55253      }
55254
55255      this.state = 'READY';
55256    }
55257    /**
55258     * Once all the starting parameters have been specified, begin
55259     * operation. This method should only be invoked from the INIT
55260     * state.
55261     *
55262     * @private
55263     */
55264    ;
55265
55266    _proto.init_ = function init_() {
55267      this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
55268      // audio data from the muxed content should be removed
55269
55270      this.resetEverything();
55271      return this.monitorBuffer_();
55272    }
55273    /**
55274     * set a playlist on the segment loader
55275     *
55276     * @param {PlaylistLoader} media the playlist to set on the segment loader
55277     */
55278    ;
55279
55280    _proto.playlist = function playlist(newPlaylist, options) {
55281      if (options === void 0) {
55282        options = {};
55283      }
55284
55285      if (!newPlaylist) {
55286        return;
55287      }
55288
55289      var oldPlaylist = this.playlist_;
55290      var segmentInfo = this.pendingSegment_;
55291      this.playlist_ = newPlaylist;
55292      this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
55293      // is always our zero-time so force a sync update each time the playlist
55294      // is refreshed from the server
55295      //
55296      // Use the INIT state to determine if playback has started, as the playlist sync info
55297      // should be fixed once requests begin (as sync points are generated based on sync
55298      // info), but not before then.
55299
55300      if (this.state === 'INIT') {
55301        newPlaylist.syncInfo = {
55302          mediaSequence: newPlaylist.mediaSequence,
55303          time: 0
55304        }; // Setting the date time mapping means mapping the program date time (if available)
55305        // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
55306        // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
55307        // be updated as the playlist is refreshed before the loader starts loading, the
55308        // program date time mapping needs to be updated as well.
55309        //
55310        // This mapping is only done for the main loader because a program date time should
55311        // map equivalently between playlists.
55312
55313        if (this.loaderType_ === 'main') {
55314          this.syncController_.setDateTimeMappingForStart(newPlaylist);
55315        }
55316      }
55317
55318      var oldId = null;
55319
55320      if (oldPlaylist) {
55321        if (oldPlaylist.id) {
55322          oldId = oldPlaylist.id;
55323        } else if (oldPlaylist.uri) {
55324          oldId = oldPlaylist.uri;
55325        }
55326      }
55327
55328      this.logger_("playlist update [" + oldId + " =>
55328 " + (newPlaylist.id || newPlaylist.uri) + "]"); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
55329      // in LIVE, we always want to update with new playlists (including refreshes)
55330
55331      this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
55332      // buffering now
55333
55334      if (this.state === 'INIT' && this.couldBeginLoading_()) {
55335        return this.init_();
55336      }
55337
55338      if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
55339        if (this.mediaIndex !== null) {
55340          // we must "resync" the segment loader when we switch renditions and
55341          // the segment loader is already synced to the previous rendition
55342          this.resyncLoader();
55343        }
55344
55345        this.currentMediaInfo_ = void 0;
55346        this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
55347
55348        return;
55349      } // we reloaded the same playlist so we are in a live scenario
55350      // and we will likely need to adjust the mediaIndex
55351
55352
55353      var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
55354      this.logger_("live window shift [" + mediaSequenceDiff + "]"); // update the mediaIndex on the SegmentLoader
55355      // this is important because we can abort a request and this value must be
55356      // equal to the last appended mediaIndex
55357
55358      if (this.mediaIndex !== null) {
55359        this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
55360        // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
55361        // new playlist was incremented by 1.
55362
55363        if (this.mediaIndex < 0) {
55364          this.mediaIndex = null;
55365          this.partIndex = null;
55366        } else {
55367          var segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
55368          // unless parts fell off of the playlist for this segment.
55369          // In that case we need to reset partIndex and resync
55370
55371          if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
55372            var mediaIndex = this.mediaIndex;
55373            this.logger_("currently processing part (index " + this.partIndex + ") no longer exists.");
55374            this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
55375            // as the part was dropped from our current playlists segment.
55376            // The mediaIndex will still be valid so keep that around.
55377
55378            this.mediaIndex = mediaIndex;
55379          }
55380        }
55381      } // update the mediaIndex on the SegmentInfo object
55382      // this is important because we will update this.mediaIndex with this value
55383      // in `handleAppendsDone_` after the segment has been successfully appended
55384
55385
55386      if (segmentInfo) {
55387        segmentInfo.mediaIndex -= mediaSequenceDiff;
55388
55389        if (segmentInfo.mediaIndex < 0) {
55390          segmentInfo.mediaIndex = null;
55391          segmentInfo.partIndex = null;
55392        } else {
55393          // we need to update the referenced segment so that timing information is
55394          // saved for the new playlist's segment, however, if the segment fell off the
55395          // playlist, we can leave the old reference and just lose the timing info
55396          if (segmentInfo.mediaIndex >= 0) {
55397            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
55398          }
55399
55400          if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
55401            segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
55402          }
55403        }
55404      }
55405
55406      this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
55407    }
55408    /**
55409     * Prevent the loader from fetching additional segments. If there
55410     * is a segment request outstanding, it will finish processing
55411     * before the loader halts. A segment loader can be unpaused by
55412     * calling load().
55413     */
55414    ;
55415
55416    _proto.pause = function pause() {
55417      if (this.checkBufferTimeout_) {
55418        window_1.clearTimeout(this.checkBufferTimeout_);
55419        this.checkBufferTimeout_ = null;
55420      }
55421    }
55422    /**
55423     * Returns whether the segment loader is fetching additional
55424     * segments when given the opportunity. This property can be
55425     * modified through calls to pause() and load().
55426     */
55427    ;
55428
55429    _proto.paused = function paused() {
55430      return this.checkBufferTimeout_ === null;
55431    }
55432    /**
55433     * Delete all the buffered data and reset the SegmentLoader
55434     *
55435     * @param {Function} [done] an optional callback to be executed when the remove
55436     * operation is complete
55437     */
55438    ;
55439
55440    _proto.resetEverything = function resetEverything(done) {
55441      this.ended_ = false;
55442      this.appendInitSegment_ = {
55443        audio: true,
55444        video: true
55445      };
55446      this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
55447      // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
55448      // we then clamp the value to duration if necessary.
55449
55450      this.remove(0, Infinity, done); // clears fmp4 captions
55451
55452      if (this.transmuxer_) {
55453        this.transmuxer_.postMessage({
55454          action: 'clearAllMp4Captions'
55455        });
55456      }
55457    }
55458    /**
55459     * Force the SegmentLoader to resync and start loading around the currentTime instead
55460     * of starting at the end of the buffer
55461     *
55462     * Useful for fast quality changes
55463     */
55464    ;
55465
55466    _proto.resetLoader = function resetLoader() {
55467      this.fetchAtBuffer_ = false;
55468      this.resyncLoader();
55469    }
55470    /**
55471     * Force the SegmentLoader to restart synchronization and make a conservative guess
55472     * before returning to the simple walk-forward method
55473     */
55474    ;
55475
55476    _proto.resyncLoader = function resyncLoader() {
55477      if (this.transmuxer_) {
55478        // need to clear out any cached data to prepare for the new segment
55479        segmentTransmuxer.reset(this.transmuxer_);
55480      }
55481
55482      this.mediaIndex = null;
55483      this.partIndex = null;
55484      this.syncPoint_ = null;
55485      this.isPendingTimestampOffset_ = false;
55486      this.callQueue_ = [];
55487      this.loadQueue_ = [];
55488      this.metadataQueue_.id3 = [];
55489      this.metadataQueue_.caption = [];
55490      this.abort();
55491
55492      if (this.transmuxer_) {
55493        this.transmuxer_.postMessage({
55494          action: 'clearParsedMp4Captions'
55495        });
55496      }
55497    }
55498    /**
55499     * Remove any data in the source buffer between start and end times
55500     *
55501     * @param {number} start - the start time of the region to remove from the buffer
55502     * @param {number} end - the end time of the region to remove from the buffer
55503     * @param {Function} [done] - an optional callback to be executed when the remove
55504     * @param {boolean} force - force all remove operations to happen
55505     * operation is complete
55506     */
55507    ;
55508
55509    _proto.remove = function remove(start, end, done, force) {
55510      if (done === void 0) {
55511        done = function done() {};
55512      }
55513
55514      if (force === void 0) {
55515        force = false;
55516      } // clamp end to duration if we need to remove everything.
55517      // This is due to a browser bug that causes issues if we remove to Infinity.
55518      // videojs/videojs-contrib-hls#1225
55519
55520
55521      if (end === Infinity) {
55522        end = this.duration_();
55523      } // skip removes that would throw an error
55524      // commonly happens during a rendition switch at the start of a video
55525      // from start 0 to end 0
55526
55527
55528      if (end <= start) {
55529        this.logger_('skipping remove because end ${end} is <= start ${start}');
55530        return;
55531      }
55532
55533      if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
55534        this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
55535
55536        return;
55537      } // set it to one to complete this function's removes
55538
55539
55540      var removesRemaining = 1;
55541
55542      var removeFinished = function removeFinished() {
55543        removesRemaining--;
55544
55545        if (removesRemaining === 0) {
55546          done();
55547        }
55548      };
55549
55550      if (force || !this.audioDisabled_) {
55551        removesRemaining++;
55552        this.sourceUpdater_.removeAudio(start, end, removeFinished);
55553      } // While it would be better to only remove video if the main loader has video, this
55554      // should be safe with audio only as removeVideo will call back even if there's no
55555      // video buffer.
55556      //
55557      // In theory we can check to see if there's video before calling the remove, but in
55558      // the event that we're switching between renditions and from video to audio only
55559      // (when we add support for that), we may need to clear the video contents despite
55560      // what the new media will contain.
55561
55562
55563      if (force || this.loaderType_ === 'main') {
55564        this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
55565        removesRemaining++;
55566        this.sourceUpdater_.removeVideo(start, end, removeFinished);
55567      } // remove any captions and ID3 tags
55568
55569
55570      for (var track in this.inbandTextTracks_) {
55571        removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
55572      }
55573
55574      removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
55575
55576      removeFinished();
55577    }
55578    /**
55579     * (re-)schedule monitorBufferTick_ to run as soon as possible
55580     *
55581     * @private
55582     */
55583    ;
55584
55585    _proto.monitorBuffer_ = function monitorBuffer_() {
55586      if (this.checkBufferTimeout_) {
55587        window_1.clearTimeout(this.checkBufferTimeout_);
55588      }
55589
55590      this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), 1);
55591    }
55592    /**
55593     * As long as the SegmentLoader is in the READY state, periodically
55594     * invoke fillBuffer_().
55595     *
55596     * @private
55597     */
55598    ;
55599
55600    _proto.monitorBufferTick_ = function monitorBufferTick_() {
55601      if (this.state === 'READY') {
55602        this.fillBuffer_();
55603      }
55604
55605      if (this.checkBufferTimeout_) {
55606        window_1.clearTimeout(this.checkBufferTimeout_);
55607      }
55608
55609      this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
55610    }
55611    /**
55612     * fill the buffer with segements unless the sourceBuffers are
55613     * currently updating
55614     *
55615     * Note: this function should only ever be called by monitorBuffer_
55616     * and never directly
55617     *
55618     * @private
55619     */
55620    ;
55621
55622    _proto.fillBuffer_ = function fillBuffer_() {
55623      // TODO since the source buffer maintains a queue, and we shouldn't call this function
55624      // except when we're ready for the next segment, this check can most likely be removed
55625      if (this.sourceUpdater_.updating()) {
55626        return;
55627      } // see if we need to begin loading immediately
55628
55629
55630      var segmentInfo = this.chooseNextRequest_();
55631
55632      if (!segmentInfo) {
55633        return;
55634      }
55635
55636      if (typeof segmentInfo.timestampOffset === 'number') {
55637        this.isPendingTimestampOffset_ = false;
55638        this.timelineChangeController_.pendingTimelineChange({
55639          type: this.loaderType_,
55640          from: this.currentTimeline_,
55641          to: segmentInfo.timeline
55642        });
55643      }
55644
55645      this.loadSegment_(segmentInfo);
55646    }
55647    /**
55648     * Determines if we should call endOfStream on the media source based
55649     * on the state of the buffer or if appened segment was the final
55650     * segment in the playlist.
55651     *
55652     * @param {number} [mediaIndex] the media index of segment we last appended
55653     * @param {Object} [playlist] a media playlist object
55654     * @return {boolean} do we need to call endOfStream on the MediaSource
55655     */
55656    ;
55657
55658    _proto.isEndOfStream_ = function isEndOfStream_(mediaIndex, playlist, partIndex) {
55659      if (mediaIndex === void 0) {
55660        mediaIndex = this.mediaIndex;
55661      }
55662
55663      if (playlist === void 0) {
55664        playlist = this.playlist_;
55665      }
55666
55667      if (partIndex === void 0) {
55668        partIndex = this.partIndex;
55669      }
55670
55671      if (!playlist || !this.mediaSource_) {
55672        return false;
55673      }
55674
55675      var segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
55676
55677      var appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
55678
55679      var appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream
55680      // so that MediaSources can trigger the `ended` event when it runs out of
55681      // buffered data instead of waiting for me
55682
55683      return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
55684    }
55685    /**
55686     * Determines what request should be made given current segment loader state.
55687     *
55688     * @return {Object} a request object that describes the segment/part to load
55689     */
55690    ;
55691
55692    _proto.chooseNextRequest_ = function chooseNextRequest_() {
55693      var bufferedEnd = lastBufferedEnd(this.buffered_()) || 0;
55694      var bufferedTime = Math.max(0, bufferedEnd - this.currentTime_());
55695      var preloaded = !this.hasPlayed_() && bufferedTime >= 1;
55696      var haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
55697      var segments = this.playlist_.segments; // return no segment if:
55698      // 1. we don't have segments
55699      // 2. The video has not yet played and we already downloaded a segment
55700      // 3. we already have enough buffered time
55701
55702      if (!segments.length || preloaded || haveEnoughBuffer) {
55703        return null;
55704      }
55705
55706      this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
55707      var next = {
55708        partIndex: null,
55709        mediaIndex: null,
55710        startOfSegment: null,
55711        playlist: this.playlist_,
55712        isSyncRequest: Boolean(!this.syncPoint_)
55713      };
55714
55715      if (next.isSyncRequest) {
55716        next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
55717      } else if (this.mediaIndex !== null) {
55718        var segment = segments[this.mediaIndex];
55719        var partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
55720        next.startOfSegment = segment.end ? segment.end : bufferedEnd;
55721
55722        if (segment.parts && segment.parts[partIndex + 1]) {
55723          next.mediaIndex = this.mediaIndex;
55724          next.partIndex = partIndex + 1;
55725        } else {
55726          next.mediaIndex = this.mediaIndex + 1;
55727        }
55728      } else {
55729        // Find the segment containing the end of the buffer or current time.
55730        var _Playlist$getMediaInf = Playlist.getMediaInfoForTime({
55731          experimentalExactManifestTimings: this.experimentalExactManifestTimings,
55732          playlist: this.playlist_,
55733          currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
55734          startingPartIndex: this.syncPoint_.partIndex,
55735          startingSegmentIndex: this.syncPoint_.segmentIndex,
55736          startTime: this.syncPoint_.time
55737        }),
55738            segmentIndex = _Playlist$getMediaInf.segmentIndex,
55739            startTime = _Playlist$getMediaInf.startTime,
55740            _partIndex = _Playlist$getMediaInf.partIndex;
55741
55742        next.getMediaInfoForTime = this.fetchAtBuffer_ ? 'bufferedEnd' : 'currentTime';
55743        next.mediaIndex = segmentIndex;
55744        next.startOfSegment = startTime;
55745        next.partIndex = _partIndex;
55746      }
55747
55748      var nextSegment = segments[next.mediaIndex];
55749      var nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex];
55749 // if the next segment index is invalid or
55750      // the next partIndex is invalid do not choose a next segment.
55751
55752      if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
55753        return null;
55754      } // if the next segment has parts, and we don't have a partIndex.
55755      // Set partIndex to 0
55756
55757
55758      if (typeof next.partIndex !== 'number' && nextSegment.parts) {
55759        next.partIndex = 0;
55760      }
55761
55762      var ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
55763      // 1. this is the last segment in the playlist
55764      // 2. end of stream has been called on the media source already
55765      // 3. the player is not seeking
55766
55767      if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
55768        return null;
55769      }
55770
55771      return this.generateSegmentInfo_(next);
55772    };
55773
55774    _proto.generateSegmentInfo_ = function generateSegmentInfo_(options) {
55775      var playlist = options.playlist,
55776          mediaIndex = options.mediaIndex,
55777          startOfSegment = options.startOfSegment,
55778          isSyncRequest = options.isSyncRequest,
55779          partIndex = options.partIndex,
55780          forceTimestampOffset = options.forceTimestampOffset,
55781          getMediaInfoForTime = options.getMediaInfoForTime;
55782      var segment = playlist.segments[mediaIndex];
55783      var part = typeof partIndex === 'number' && segment.parts[partIndex];
55784      var segmentInfo = {
55785        requestId: 'segment-loader-' + Math.random(),
55786        // resolve the segment URL relative to the playlist
55787        uri: part && part.resolvedUri || segment.resolvedUri,
55788        // the segment's mediaIndex at the time it was requested
55789        mediaIndex: mediaIndex,
55790        partIndex: part ? partIndex : null,
55791        // whether or not to update the SegmentLoader's state with this
55792        // segment's mediaIndex
55793        isSyncRequest: isSyncRequest,
55794        startOfSegment: startOfSegment,
55795        // the segment's playlist
55796        playlist: playlist,
55797        // unencrypted bytes of the segment
55798        bytes: null,
55799        // when a key is defined for this segment, the encrypted bytes
55800        encryptedBytes: null,
55801        // The target timestampOffset for this segment when we append it
55802        // to the source buffer
55803        timestampOffset: null,
55804        // The timeline that the segment is in
55805        timeline: segment.timeline,
55806        // The expected duration of the segment in seconds
55807        duration: part && part.duration || segment.duration,
55808        // retain the segment in case the playlist updates while doing an async process
55809        segment: segment,
55810        part: part,
55811        byteLength: 0,
55812        transmuxer: this.transmuxer_,
55813        // type of getMediaInfoForTime that was used to get this segment
55814        getMediaInfoForTime: getMediaInfoForTime
55815      };
55816      var overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
55817      segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
55818        segmentTimeline: segment.timeline,
55819        currentTimeline: this.currentTimeline_,
55820        startOfSegment: startOfSegment,
55821        buffered: this.buffered_(),
55822        overrideCheck: overrideCheck
55823      });
55824      var audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
55825
55826      if (typeof audioBufferedEnd === 'number') {
55827        // since the transmuxer is using the actual timing values, but the buffer is
55828        // adjusted by the timestamp offset, we must adjust the value here
55829        segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
55830      }
55831
55832      if (this.sourceUpdater_.videoBuffered().length) {
55833        segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_, // since the transmuxer is using the actual timing values, but the time is
55834        // adjusted by the timestmap offset, we must adjust the value here
55835        this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
55836      }
55837
55838      return segmentInfo;
55839    } // get the timestampoffset for a segment,
55840    // added so that vtt segment loader can override and prevent
55841    // adding timestamp offsets.
55842    ;
55843
55844    _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_(options) {
55845      return timestampOffsetForSegment(options);
55846    }
55847    /**
55848     * Determines if the network has enough bandwidth to complete the current segment
55849     * request in a timely manner. If not, the request will be aborted early and bandwidth
55850     * updated to trigger a playlist switch.
55851     *
55852     * @param {Object} stats
55853     *        Object containing stats about the request timing and size
55854     * @private
55855     */
55856    ;
55857
55858    _proto.earlyAbortWhenNeeded_ = function earlyAbortWhenNeeded_(stats) {
55859      if (this.vhs_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
55860      // TODO: Replace using timeout with a boolean indicating whether this playlist is
55861      //       the lowestEnabledRendition.
55862      !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
55863      !this.playlist_.attributes.BANDWIDTH) {
55864        return;
55865      } // Wait at least 1 second since the first byte of data has been received before
55866      // using the calculated bandwidth from the progress event to allow the bitrate
55867      // to stabilize
55868
55869
55870      if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
55871        return;
55872      }
55873
55874      var currentTime = this.currentTime_();
55875      var measuredBandwidth = stats.bandwidth;
55876      var segmentDuration = this.pendingSegment_.duration;
55877      var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
55878      // if we are only left with less than 1 second when the request completes.
55879      // A negative timeUntilRebuffering indicates we are already rebuffering
55880
55881      var timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
55882      // is larger than the estimated time until the player runs out of forward buffer
55883
55884      if (requestTimeRemaining <= timeUntilRebuffer$1) {
55885        return;
55886      }
55887
55888      var switchCandidate = minRebufferMaxBandwidthSelector({
55889        master: this.vhs_.playlists.master,
55890        currentTime: currentTime,
55891        bandwidth: measuredBandwidth,
55892        duration: this.duration_(),
55893        segmentDuration: segmentDuration,
55894        timeUntilRebuffer: timeUntilRebuffer$1,
55895        currentTimeline: this.currentTimeline_,
55896        syncController: this.syncController_
55897      });
55898
55899      if (!switchCandidate) {
55900        return;
55901      }
55902
55903      var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
55904      var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
55905      var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
55906      // potential round trip time of the new request so that we are not too aggressive
55907      // with switching to a playlist that might save us a fraction of a second.
55908
55909      if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
55910        minimumTimeSaving = 1;
55911      }
55912
55913      if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
55914        return;
55915      } // set the bandwidth to that of the desired playlist being sure to scale by
55916      // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
55917      // don't trigger a bandwidthupdate as the bandwidth is artifial
55918
55919
55920      this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
55921      this.trigger('earlyabort');
55922    };
55923
55924    _proto.handleAbort_ = function handleAbort_(segmentInfo) {
55925      this.logger_("Aborting " + segmentInfoString(segmentInfo));
55926      this.mediaRequestsAborted += 1;
55927    }
55928    /**
55929     * XHR `progress` event handler
55930     *
55931     * @param {Event}
55932     *        The XHR `progress` event
55933     * @param {Object} simpleSegment
55934     *        A simplified segment object copy
55935     * @private
55936     */
55937    ;
55938
55939    _proto.handleProgress_ = function handleProgress_(event, simpleSegment) {
55940      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55941
55942      if (this.checkForAbort_(simpleSegment.requestId)) {
55943        return;
55944      }
55945
55946      this.trigger('progress');
55947    };
55948
55949    _proto.handleTrackInfo_ = function handleTrackInfo_(simpleSegment, trackInfo) {
55950      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55951
55952      if (this.checkForAbort_(simpleSegment.requestId)) {
55953        return;
55954      }
55955
55956      if (this.checkForIllegalMediaSwitch(trackInfo)) {
55957        return;
55958      }
55959
55960      trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
55961      // Guard against cases where we're not getting track info at all until we are
55962      // certain that all streams will provide it.
55963
55964      if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
55965        this.appendInitSegment_ = {
55966          audio: true,
55967          video: true
55968        };
55969        this.startingMediaInfo_ = trackInfo;
55970        this.currentMediaInfo_ = trackInfo;
55971        this.logger_('trackinfo update', trackInfo);
55972        this.trigger('trackinfo');
55973      } // trackinfo may cause an abort if the trackinfo
55974      // causes a codec change to an unsupported codec.
55975
55976
55977      if (this.checkForAbort_(simpleSegment.requestId)) {
55978        return;
55979      } // set trackinfo on the pending segment so that
55980      // it can append.
55981
55982
55983      this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
55984
55985      if (this.hasEnoughInfoToAppend_()) {
55986        this.processCallQueue_();
55987      }
55988    };
55989
55990    _proto.handleTimingInfo_ = function handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
55991      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55992
55993      if (this.checkForAbort_(simpleSegment.requestId)) {
55994        return;
55995      }
55996
55997      var segmentInfo = this.pendingSegment_;
55998      var timingInfoProperty = timingInfoPropertyForMedia(mediaType);
55999      segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
56000      segmentInfo[timingInfoProperty][timeType] = time;
56001      this.logger_("timinginfo: " + mediaType + " - " + timeType + " - " + time); // check if any calls were waiting on the timing info
56002
56003      if (this.hasEnoughInfoToAppend_()) {
56004        this.processCallQueue_();
56005      }
56006    };
56007
56008    _proto.handleCaptions_ = function handleCaptions_(simpleSegment, captionData) {
56009      var _this2 = this;
56010
56011      this.earlyAbortWhenNeeded_(simpleSegment.stats);
56012
56013      if (this.checkForAbort_(simpleSegment.requestId)) {
56014        return;
56015      } // This could only happen with fmp4 segments, but
56016      // should still not happen in general
56017
56018
56019      if (captionData.length === 0) {
56020        this.logger_('SegmentLoader received no captions from a caption event');
56021        return;
56022      }
56023
56024      var segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
56025      // can be adjusted by the timestamp offset
56026
56027      if (!segmentInfo.hasAppendedData_) {
56028        this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
56029        return;
56030      }
56031
56032      var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
56033      var captionTracks = {}; // get total start/end and captions for each track/stream
56034
56035      captionData.forEach(function (caption) {
56036        // caption.stream is actually a track name...
56037        // set to the existing values in tracks or default values
56038        captionTracks[caption.stream] = captionTracks[caption.stream] || {
56039          // Infinity, as any other value will be less than this
56040          startTime: Infinity,
56041          captions: [],
56042          // 0 as an other value will be more than this
56043          endTime: 0
56044        };
56045        var captionTrack = captionTracks[caption.stream];
56046        captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
56047        captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
56048        captionTrack.captions.push(caption);
56049      });
56050      Object.keys(captionTracks).forEach(function (trackName) {
56051        var _captionTracks$trackN = captionTracks[trackName],
56052            startTime = _captionTracks$trackN.startTime,
56053            endTime = _captionTracks$trackN.endTime,
56054            captions = _captionTracks$trackN.captions;
56055        var inbandTextTracks = _this2.inbandTextTracks_;
56056
56057        _this2.logger_("adding cues from " + startTime + " -> " + endTime + " for " + trackName);
56058
56059        createCaptionsTrackIfNotExists(inbandTextTracks, _this2.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
56060        // We do this because a rendition change that also changes the timescale for captions
56061        // will result in captions being re-parsed for certain segments. If we add them again
56062        // without clearing we will have two of the same captions visible.
56063
56064        removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
56065        addCaptionData({
56066          captionArray: captions,
56067          inbandTextTracks: inbandTextTracks,
56068          timestampOffset: timestampOffset
56069        });
56070      }); // Reset stored captions since we added parsed
56071      // captions to a text track at this point
56072
56073      if (this.transmuxer_) {
56074        this.transmuxer_.postMessage({
56075          action: 'clearParsedMp4Captions'
56076        });
56077      }
56078    };
56079
56080    _proto.handleId3_ = function handleId3_(simpleSegment, id3Frames, dispatchType) {
56081      this.earlyAbortWhenNeeded_(simpleSegment.stats);
56082
56083      if (this.checkForAbort_(simpleSegment.requestId)) {
56084        return;
56085      }
56086
56087      var segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
56088
56089      if (!segmentInfo.hasAppendedData_) {
56090        this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
56091        return;
56092      }
56093
56094      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
56095      // audio/video source with a metadata track, and an alt audio with a metadata track.
56096      // However, this probably won't happen, and if it does it can be handled then.
56097
56098      createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_);
56099      addMetadata({
56100        inbandTextTracks: this.inbandTextTracks_,
56101        metadataArray: id3Frames,
56102        timestampOffset: timestampOffset,
56103        videoDuration: this.duration_()
56104      });
56105    };
56106
56107    _proto.processMetadataQueue_ = function processMetadataQueue_() {
56108      this.metadataQueue_.id3.forEach(function (fn) {
56109        return fn();
56110      });
56111      this.metadataQueue_.caption.forEach(function (fn) {
56112        return fn();
56113      });
56114      this.metadataQueue_.id3 = [];
56115      this.metadataQueue_.caption = [];
56116    };
56117
56118    _proto.processCallQueue_ = function processCallQueue_() {
56119      var callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
56120      // functions may check the length of the load queue and default to pushing themselves
56121      // back onto the queue.
56122
56123      this.callQueue_ = [];
56124      callQueue.forEach(function (fun) {
56125        return fun();
56126      });
56127    };
56128
56129    _proto.processLoadQueue_ = function processLoadQueue_() {
56130      var loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
56131      // functions may check the length of the load queue and default to pushing themselves
56132      // back onto the queue.
56133
56134      this.loadQueue_ = [];
56135      loadQueue.forEach(function (fun) {
56136        return fun();
56137      });
56138    }
56139    /**
56140     * Determines whether the loader has enough info to load the next segment.
56141     *
56142     * @return {boolean}
56143     *         Whether or not the loader has enough info to load the next segment
56144     */
56145    ;
56146
56147    _proto.hasEnoughInfoToLoad_ = function hasEnoughInfoToLoad_() {
56148      // Since primary timing goes by video, only the audio loader potentially needs to wait
56149      // to load.
56150      if (this.loaderType_ !== 'audio') {
56151        return true;
56152      }
56153
56154      var segmentInfo = this.pendingSegment_;
56154 // A fill buffer must have already run to establish a pending segment before there's
56155      // enough info to load.
56156
56157      if (!segmentInfo) {
56158        return false;
56159      } // The first segment can and should be loaded immediately so that source buffers are
56160      // created together (before appending). Source buffer creation uses the presence of
56161      // audio and video data to determine whether to create audio/video source buffers, and
56162      // uses processed (transmuxed or parsed) media to determine the types required.
56163
56164
56165      if (!this.getCurrentMediaInfo_()) {
56166        return true;
56167      }
56168
56169      if ( // Technically, instead of waiting to load a segment on timeline changes, a segment
56170      // can be requested and downloaded and only wait before it is transmuxed or parsed.
56171      // But in practice, there are a few reasons why it is better to wait until a loader
56172      // is ready to append that segment before requesting and downloading:
56173      //
56174      // 1. Because audio and main loaders cross discontinuities together, if this loader
56175      //    is waiting for the other to catch up, then instead of requesting another
56176      //    segment and using up more bandwidth, by not yet loading, more bandwidth is
56177      //    allotted to the loader currently behind.
56178      // 2. media-segment-request doesn't have to have logic to consider whether a segment
56179      // is ready to be processed or not, isolating the queueing behavior to the loader.
56180      // 3. The audio loader bases some of its segment properties on timing information
56181      //    provided by the main loader, meaning that, if the logic for waiting on
56182      //    processing was in media-segment-request, then it would also need to know how
56183      //    to re-generate the segment information after the main loader caught up.
56184      shouldWaitForTimelineChange({
56185        timelineChangeController: this.timelineChangeController_,
56186        currentTimeline: this.currentTimeline_,
56187        segmentTimeline: segmentInfo.timeline,
56188        loaderType: this.loaderType_,
56189        audioDisabled: this.audioDisabled_
56190      })) {
56191        return false;
56192      }
56193
56194      return true;
56195    };
56196
56197    _proto.getCurrentMediaInfo_ = function getCurrentMediaInfo_(segmentInfo) {
56198      if (segmentInfo === void 0) {
56199        segmentInfo = this.pendingSegment_;
56200      }
56201
56202      return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
56203    };
56204
56205    _proto.getMediaInfo_ = function getMediaInfo_(segmentInfo) {
56206      if (segmentInfo === void 0) {
56207        segmentInfo = this.pendingSegment_;
56208      }
56209
56210      return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
56211    };
56212
56213    _proto.hasEnoughInfoToAppend_ = function hasEnoughInfoToAppend_() {
56214      if (!this.sourceUpdater_.ready()) {
56215        return false;
56216      } // If content needs to be removed or the loader is waiting on an append reattempt,
56217      // then no additional content should be appended until the prior append is resolved.
56218
56219
56220      if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
56221        return false;
56222      }
56223
56224      var segmentInfo = this.pendingSegment_;
56225      var trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
56226      // we do not have information on this specific
56227      // segment yet
56228
56229      if (!segmentInfo || !trackInfo) {
56230        return false;
56231      }
56232
56233      var hasAudio = trackInfo.hasAudio,
56234          hasVideo = trackInfo.hasVideo,
56235          isMuxed = trackInfo.isMuxed;
56236
56237      if (hasVideo && !segmentInfo.videoTimingInfo) {
56238        return false;
56239      } // muxed content only relies on video timing information for now.
56240
56241
56242      if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
56243        return false;
56244      }
56245
56246      if (shouldWaitForTimelineChange({
56247        timelineChangeController: this.timelineChangeController_,
56248        currentTimeline: this.currentTimeline_,
56249        segmentTimeline: segmentInfo.timeline,
56250        loaderType: this.loaderType_,
56251        audioDisabled: this.audioDisabled_
56252      })) {
56253        return false;
56254      }
56255
56256      return true;
56257    };
56258
56259    _proto.handleData_ = function handleData_(simpleSegment, result) {
56260      this.earlyAbortWhenNeeded_(simpleSegment.stats);
56261
56262      if (this.checkForAbort_(simpleSegment.requestId)) {
56263        return;
56264      } // If there's anything in the call queue, then this data came later and should be
56265      // executed after the calls currently queued.
56266
56267
56268      if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
56269        this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
56270        return;
56271      }
56272
56273      var segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
56274
56275      this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
56276
56277      this.updateMediaSecondsLoaded_(segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
56278      // logic may change behavior depending on the state, and changing state too early may
56279      // inflate our estimates of bandwidth. In the future this should be re-examined to
56280      // note more granular states.
56281      // don't process and append data if the mediaSource is closed
56282
56283      if (this.mediaSource_.readyState === 'closed') {
56284        return;
56285      } // if this request included an initialization segment, save that data
56286      // to the initSegment cache
56287
56288
56289      if (simpleSegment.map) {
56290        simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
56291
56292        segmentInfo.segment.map = simpleSegment.map;
56293      } // if this request included a segment key, save that data in the cache
56294
56295
56296      if (simpleSegment.key) {
56297        this.segmentKey(simpleSegment.key, true);
56298      }
56299
56300      segmentInfo.isFmp4 = simpleSegment.isFmp4;
56301      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
56302
56303      if (segmentInfo.isFmp4) {
56304        this.trigger('fmp4');
56305        segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
56306      } else {
56307        var trackInfo = this.getCurrentMediaInfo_();
56308        var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
56309        var firstVideoFrameTimeForData;
56310
56311        if (useVideoTimingInfo) {
56312          firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
56313        } // Segment loader knows more about segment timing than the transmuxer (in certain
56314        // aspects), so make any changes required for a more accurate start time.
56315        // Don't set the end time yet, as the segment may not be finished processing.
56316
56317
56318        segmentInfo.timingInfo.start = this.trueSegmentStart_({
56319          currentStart: segmentInfo.timingInfo.start,
56320          playlist: segmentInfo.playlist,
56321          mediaIndex: segmentInfo.mediaIndex,
56322          currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
56323          useVideoTimingInfo: useVideoTimingInfo,
56324          firstVideoFrameTimeForData: firstVideoFrameTimeForData,
56325          videoTimingInfo: segmentInfo.videoTimingInfo,
56326          audioTimingInfo: segmentInfo.audioTimingInfo
56327        });
56328      } // Init segments for audio and video only need to be appended in certain cases. Now
56329      // that data is about to be appended, we can check the final cases to determine
56330      // whether we should append an init segment.
56331
56332
56333      this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
56334      // as we use the start of the segment to offset the best guess (playlist provided)
56335      // timestamp offset.
56336
56337      this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
56338      // be appended or not.
56339
56340      if (segmentInfo.isSyncRequest) {
56341        // first save/update our timing info for this segment.
56342        // this is what allows us to choose an accurate segment
56343        // and the main reason we make a sync request.
56344        this.updateTimingInfoEnd_(segmentInfo);
56345        this.syncController_.saveSegmentTimingInfo({
56346          segmentInfo: segmentInfo,
56347          shouldSaveTimelineMapping: this.loaderType_ === 'main'
56348        });
56349        var next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
56350        // after taking into account its timing info, do not append it.
56351
56352        if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
56353          this.logger_('sync segment was incorrect, not appending');
56354          return;
56355        } // otherwise append it like any other segment as our guess was correct.
56356
56357
56358        this.logger_('sync segment was correct, appending');
56359      } // Save some state so that in the future anything waiting on first append (and/or
56360      // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
56361      // we need some notion of whether the timestamp offset or other relevant information
56362      // has had a chance to be set.
56363
56364
56365      segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
56366
56367      this.processMetadataQueue_();
56368      this.appendData_(segmentInfo, result);
56369    };
56370
56371    _proto.updateAppendInitSegmentStatus = function updateAppendInitSegmentStatus(segmentInfo, type) {
56372      // alt audio doesn't manage timestamp offset
56373      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
56374      // segment for each chunk
56375      !segmentInfo.changedTimestampOffset) {
56376        // if the timestamp offset changed, the timeline may have changed, so we have to re-
56377        // append init segments
56378        this.appendInitSegment_ = {
56379          audio: true,
56380          video: true
56381        };
56382      }
56383
56384      if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
56385        // make sure we append init segment on playlist changes, in case the media config
56386        // changed
56387        this.appendInitSegment_[type] = true;
56388      }
56389    };
56390
56391    _proto.getInitSegmentAndUpdateState_ = function getInitSegmentAndUpdateState_(_ref4) {
56392      var type = _ref4.type,
56393          initSegment = _ref4.initSegment,
56394          map = _ref4.map,
56395          playlist = _ref4.playlist; // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
56396      // (Section 3) required to parse the applicable Media Segments.  It applies to every
56397      // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
56398      // or until the end of the playlist."
56399      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
56400
56401      if (map) {
56402        var id = initSegmentId(map);
56403
56404        if (this.activeInitSegmentId_ === id) {
56405          // don't need to re-append the init segment if the ID matches
56406          return null;
56407        } // a map-specified init segment takes priority over any transmuxed (or otherwise
56408        // obtained) init segment
56409        //
56410        // this also caches the init segment for later use
56411
56412
56413        initSegment = this.initSegmentForMap(map, true).bytes;
56414        this.activeInitSegmentId_ = id;
56415      }
56415 // We used to always prepend init segments for video, however, that shouldn't be
56416      // necessary. Instead, we should only append on changes, similar to what we've always
56417      // done for audio. This is more important (though may not be that important) for
56418      // frame-by-frame appending for LHLS, simply because of the increased quantity of
56419      // appends.
56420
56421
56422      if (initSegment && this.appendInitSegment_[type]) {
56423        // Make sure we track the playlist that we last used for the init segment, so that
56424        // we can re-append the init segment in the event that we get data from a new
56425        // playlist. Discontinuities and track changes are handled in other sections.
56426        this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
56427
56428        this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
56429        // we are appending the muxer init segment
56430
56431        this.activeInitSegmentId_ = null;
56432        return initSegment;
56433      }
56434
56435      return null;
56436    };
56437
56438    _proto.handleQuotaExceededError_ = function handleQuotaExceededError_(_ref5, error) {
56439      var _this3 = this;
56440
56441      var segmentInfo = _ref5.segmentInfo,
56442          type = _ref5.type,
56443          bytes = _ref5.bytes;
56444      var audioBuffered = this.sourceUpdater_.audioBuffered();
56445      var videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
56446      // should be cleared out during the buffer removals. However, log in case it helps
56447      // debug.
56448
56449      if (audioBuffered.length > 1) {
56450        this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
56451      }
56452
56453      if (videoBuffered.length > 1) {
56454        this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
56455      }
56456
56457      var audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
56458      var audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
56459      var videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
56460      var videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
56461
56462      if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
56463        // Can't remove enough buffer to make room for new segment (or the browser doesn't
56464        // allow for appends of segments this size). In the future, it may be possible to
56465        // split up the segment and append in pieces, but for now, error out this playlist
56466        // in an attempt to switch to a more manageable rendition.
56467        this.logger_('On QUOTA_EXCEEDED_ERR, single segment too large to append to ' + 'buffer, triggering an error. ' + ("Appended byte length: " + bytes.byteLength + ", ") + ("audio buffer: " + timeRangesToArray(audioBuffered).join(', ') + ", ") + ("video buffer: " + timeRangesToArray(videoBuffered).join(', ') + ", "));
56468        this.error({
56469          message: 'Quota exceeded error with append of a single segment of content',
56470          excludeUntil: Infinity
56471        });
56472        this.trigger('error');
56473        return;
56474      } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
56475      // that the segment-loader should block on future events until this one is handled, so
56476      // that it doesn't keep moving onto further segments. Adding the call to the call
56477      // queue will prevent further appends until waitingOnRemove_ and
56478      // quotaExceededErrorRetryTimeout_ are cleared.
56479      //
56480      // Note that this will only block the current loader. In the case of demuxed content,
56481      // the other load may keep filling as fast as possible. In practice, this should be
56482      // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
56483      // source buffer, or video fills without enough room for audio to append (and without
56484      // the availability of clearing out seconds of back buffer to make room for audio).
56485      // But it might still be good to handle this case in the future as a TODO.
56486
56487
56488      this.waitingOnRemove_ = true;
56489      this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
56490        segmentInfo: segmentInfo,
56491        type: type,
56492        bytes: bytes
56493      }));
56494      var currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
56495      // before retrying.
56496
56497      var timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
56498      this.logger_("On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to " + timeToRemoveUntil);
56499      this.remove(0, timeToRemoveUntil, function () {
56500        _this3.logger_("On QUOTA_EXCEEDED_ERR, retrying append in " + MIN_BACK_BUFFER + "s");
56501
56502        _this3.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
56503        // attempts (since we can't clear less than the minimum)
56504
56505        _this3.quotaExceededErrorRetryTimeout_ = window_1.setTimeout(function () {
56506          _this3.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
56507
56508          _this3.quotaExceededErrorRetryTimeout_ = null;
56509
56510          _this3.processCallQueue_();
56511        }, MIN_BACK_BUFFER * 1000);
56512      }, true);
56513    };
56514
56515    _proto.handleAppendError_ = function handleAppendError_(_ref6, error) {
56516      var segmentInfo = _ref6.segmentInfo,
56517          type = _ref6.type,
56518          bytes = _ref6.bytes; // if there's no error, nothing to do
56519
56520      if (!error) {
56521        return;
56522      }
56523
56524      if (error.code === QUOTA_EXCEEDED_ERR) {
56525        this.handleQuotaExceededError_({
56526          segmentInfo: segmentInfo,
56527          type: type,
56528          bytes: bytes
56529        }); // A quota exceeded error should be recoverable with a future re-append, so no need
56530        // to trigger an append error.
56531
56532        return;
56533      }
56534
56535      this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error);
56536      this.error(type + " append of " + bytes.length + "b failed for segment " + ("#" + segmentInfo.mediaIndex + " in playlist " + segmentInfo.playlist.id)); // If an append errors, we often can't recover.
56537      // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
56538      //
56539      // Trigger a special error so that it can be handled separately from normal,
56540      // recoverable errors.
56541
56542      this.trigger('appenderror');
56543    };
56544
56545    _proto.appendToSourceBuffer_ = function appendToSourceBuffer_(_ref7) {
56546      var segmentInfo = _ref7.segmentInfo,
56547          type = _ref7.type,
56548          initSegment = _ref7.initSegment,
56549          data = _ref7.data,
56550          bytes = _ref7.bytes; // If this is a re-append, bytes were already created and don't need to be recreated
56551
56552      if (!bytes) {
56553        var segments = [data];
56554        var byteLength = data.byteLength;
56555
56556        if (initSegment) {
56557          // if the media initialization segment is changing, append it before the content
56558          // segment
56559          segments.unshift(initSegment);
56560          byteLength += initSegment.byteLength;
56561        } // Technically we should be OK appending the init segment separately, however, we
56562        // haven't yet tested that, and prepending is how we have always done things.
56563
56564
56565        bytes = concatSegments({
56566          bytes: byteLength,
56567          segments: segments
56568        });
56569      }
56570
56571      this.sourceUpdater_.appendBuffer({
56572        segmentInfo: segmentInfo,
56573        type: type,
56574        bytes: bytes
56575      }, this.handleAppendError_.bind(this, {
56576        segmentInfo: segmentInfo,
56577        type: type,
56578        bytes: bytes
56579      }));
56580    };
56581
56582    _proto.handleSegmentTimingInfo_ = function handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
56583      if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
56584        return;
56585      }
56586
56587      var segment = this.pendingSegment_.segment;
56588      var timingInfoProperty = type + "TimingInfo";
56589
56590      if (!segment[timingInfoProperty]) {
56591        segment[timingInfoProperty] = {};
56592      }
56593
56594      segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
56595      segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
56596      segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
56597      segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
56598      segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
56599
56600      segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
56601    };
56602
56603    _proto.appendData_ = function appendData_(segmentInfo, result) {
56604      var type = result.type,
56605          data = result.data;
56606
56607      if (!data || !data.byteLength) {
56608        return;
56609      }
56610
56611      if (type === 'audio' && this.audioDisabled_) {
56612        return;
56613      }
56614
56615      var initSegment = this.getInitSegmentAndUpdateState_({
56616        type: type,
56617        initSegment: result.initSegment,
56618        playlist: segmentInfo.playlist,
56619        map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
56620      });
56621      this.appendToSourceBuffer_({
56622        segmentInfo: segmentInfo,
56623        type: type,
56624        initSegment: initSegment,
56625        data: data
56626      });
56627    }
56628    /**
56629     * load a specific segment from a request into the buffer
56630     *
56631     * @private
56632     */
56633    ;
56634
56635    _proto.loadSegment_ = function loadSegment_(segmentInfo) {
56636      var _this4 = this;
56637
56638      this.state = 'WAITING';
56639      this.pendingSegment_ = segmentInfo;
56640      this.trimBackBuffer_(segmentInfo);
56641
56642      if (typeof segmentInfo.timestampOffset === 'number') {
56643        if (this.transmuxer_) {
56644          this.transmuxer_.postMessage({
56645            action: 'clearAllMp4Captions'
56646          });
56647        }
56648      }
56649
56650      if (!this.hasEnoughInfoToLoad_()) {
56651        this.loadQueue_.push(function () {
56652          // regenerate the audioAppendStart, timestampOffset, etc as they
56653          // may have changed since this function was added to the queue.
56654          var options = _extends_1({}, segmentInfo, {
56655            forceTimestampOffset: true
56656          });
56657
56658          _extends_1(segmentInfo, _this4.generateSegmentInfo_(options));
56659
56660          _this4.isPendingTimestampOffset_ = false;
56661
56662          _this4.updateTransmuxerAndRequestSegment_(segmentInfo);
56663        });
56664        return;
56665      }
56666
56667      this.updateTransmuxerAndRequestSegment_(segmentInfo);
56668    };
56669
56670    _proto.updateTransmuxerAndRequestSegment_ = function updateTransmuxerAndRequestSegment_(segmentInfo) {
56671      var _this5 = this; // We'll update the source buffer's timestamp offset once we have transmuxed data, but
56672      // the transmuxer still needs to be updated before then.
56673      //
56674      // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
56675      // offset must be passed to the transmuxer for stream correcting adjustments.
56676
56677
56678      if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
56679        this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
56680
56681        segmentInfo.gopsToAlignWith = [];
56682        this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
56683
56684        this.transmuxer_.postMessage({
56685          action: 'reset'
56686        });
56687        this.transmuxer_.postMessage({
56688          action: 'setTimestampOffset',
56689          timestampOffset: segmentInfo.timestampOffset
56690        });
56691      }
56692
56693      var simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
56694      var isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
56695      var isWalkingForward = this.mediaIndex !== null;
56696      var isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ && // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
56697      // the first timeline
56698      segmentInfo.timeline > 0;
56699      var isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
56700      this.logger_("Requesting " + segmentInfoString(segmentInfo)); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
56701      // then this init segment has never been seen before and should be appended.
56702      //
56703      // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
56704      // both to true and leave the decision of whether to append the init segment to append time.
56705
56706      if (simpleSegment.map && !simpleSegment.map.bytes) {
56707        this.logger_('going to request init segment.');
56708        this.appendInitSegment_ = {
56709          video: true,
56710          audio: true
56711        };
56712      }
56713
56714      segmentInfo.abortRequests = mediaSegmentRequest({
56715        xhr: this.vhs_.xhr,
56716        xhrOptions: this.xhrOptions_,
56717        decryptionWorker: this.decrypter_,
56718        segment: simpleSegment,
56719        abortFn: this.handleAbort_.bind(this, segmentInfo),
56720        progressFn: this.handleProgress_.bind(this),
56721        trackInfoFn: this.handleTrackInfo_.bind(this),
56722        timingInfoFn: this.handleTimingInfo_.bind(this),
56723        videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
56724        audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
56725        captionsFn: this.handleCaptions_.bind(this),
56726        isEndOfTimeline: isEndOfTimeline,
56727        endedTimelineFn: function endedTimelineFn() {
56728          _this5.logger_('received endedtimeline callback');
56729        },
56730        id3Fn: this.handleId3_.bind(this),
56731        dataFn: this.handleData_.bind(this),
56732        doneFn: this.segmentRequestFinished_.bind(this),
56733        onTransmuxerLog: function onTransmuxerLog(_ref8) {
56734          var message = _ref8.message,
56735              level = _ref8.level,
56736              stream = _ref8.stream;
56737
56738          _this5.logger_(segmentInfoString(segmentInfo) + " logged from transmuxer stream " + stream + " as a " + level + ": " + message);
56739        }
56740      });
56741    }
56742    /**
56743     * trim the back buffer so that we don't have too much data
56744     * in the source buffer
56745     *
56746     * @private
56747     *
56748     * @param {Object} segmentInfo - the current segment
56749     */
56750    ;
56751
56752    _proto.trimBackBuffer_ = function trimBackBuffer_(segmentInfo) {
56753      var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
56754      // buffer and a very conservative "garbage collector"
56755      // We manually clear out the old buffer to ensure
56756      // we don't trigger the QuotaExceeded error
56757      // on the source buffer during subsequent appends
56758
56759      if (removeToTime > 0) {
56760        this.remove(0, removeToTime);
56761      }
56762    }
56763    /**
56764     * created a simplified copy of the segment object with just the
56765     * information necessary to perform the XHR and decryption
56766     *
56767     * @private
56768     *
56769     * @param {Object} segmentInfo - the current segment
56770     * @return {Object} a simplified segment object copy
56771     */
56772    ;
56773
56774    _proto.createSimplifiedSegmentObj_ = function createSimplifiedSegmentObj_(segmentInfo) {
56775      var segment = segmentInfo.segment;
56776      var part = segmentInfo.part;
56777      var simpleSegment = {
56778        resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
56779        byterange: part ? part.byterange : segment.byterange,
56780        requestId: segmentInfo.requestId,
56781        transmuxer: segmentInfo.transmuxer,
56782        audioAppendStart: segmentInfo.audioAppendStart,
56783        gopsToAlignWith: segmentInfo.gopsToAlignWith,
56784        part: segmentInfo.part
56785      };
56786      var previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
56787
56788      if (previousSegment && previousSegment.timeline === segment.timeline) {
56789        // The baseStartTime of a segment is used to handle rollover when probing the TS
56790        // segment to retrieve timing information. Since the probe only looks at the media's
56791        // times (e.g., PTS and DTS values of the segment), and doesn't consider the
56792        // player's time (e.g., player.currentTime()), baseStartTime should reflect the
56793        // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
56794        // seconds of media time, so should be used here. The previous segment is used since
56795        // the end of the previous segment should represent the beginning of the current
56796        // segment, so long as they are on the same timeline.
56797        if (previousSegment.videoTimingInfo) {
56798          simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
56799        } else if (previousSegment.audioTimingInfo) {
56800          simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
56801        }
56802      }
56803
56804      if (segment.key) {
56805        // if the media sequence is greater than 2^32, the IV will be incorrect
56806        // assuming 10s segments, that would be about 1300 years
56807        var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
56808        simpleSegment.key = this.segmentKey(segment.key);
56809        simpleSegment.key.iv = iv;
56810      }
56811
56812      if (segment.map) {
56813        simpleSegment.map = this.initSegmentForMap(segment.map);
56814      }
56815
56816      return simpleSegment;
56817    };
56818
56819    _proto.saveTransferStats_ = function saveTransferStats_(stats) {
56820      // every request counts as a media request even if it has been aborted
56821      // or canceled due to a timeout
56822      this.mediaRequests += 1;
56823
56824      if (stats) {
56825        this.mediaBytesTransferred += stats.bytesReceived;
56826        this.mediaTransferDuration += stats.roundTripTime;
56827      }
56828    };
56829
56830    _proto.saveBandwidthRelatedStats_ = function saveBandwidthRelatedStats_(duration, stats) {
56831      // byteLength will be used for throughput, and should be based on bytes receieved,
56832      // which we only know at the end of the request and should reflect total bytes
56833      // downloaded rather than just bytes processed from components of the segment
56834      this.pendingSegment_.byteLength = stats.bytesReceived;
56835
56836      if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
56837        this.logger_("Ignoring segment's bandwidth because its duration of " + duration + (" is less than the min to record " + MIN_SEGMENT_DURATION_TO_SAVE_STATS));
56838        return;
56839      }
56840
56841      this.bandwidth = stats.bandwidth;
56842      this.roundTrip = stats.roundTripTime;
56843    };
56844
56845    _proto.handleTimeout_ = function handleTimeout_() {
56846      // although the VTT segment loader bandwidth isn't really used, it's good to
56847      // maintain functinality between segment loaders
56848      this.mediaRequestsTimedout += 1;
56849      this.bandwidth = 1;
56850      this.roundTrip = NaN;
56851      this.trigger('bandwidthupdate');
56852    }
56853    /**
56854     * Handle the callback from the segmentRequest function and set the
56855     * associated SegmentLoader state and errors if necessary
56856     *
56857     * @private
56858     */
56859    ;
56860
56861    _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
56862      // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
56863      // check the call queue directly since this function doesn't need to deal with any
56864      // data, and can continue even if the source buffers are not set up and we didn't get
56865      // any data from the segment
56866      if (this.callQueue_.length) {
56867        this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
56868        return;
56869      }
56870
56871      this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
56872
56873      if (!this.pendingSegment_) {
56874        return;
56875      } // the request was aborted and the SegmentLoader has already started
56876      // another request. this can happen when the timeout for an aborted
56877      // request triggers due to a limitation in the XHR library
56878      // do not count this as any sort of request or we risk double-counting
56879
56880
56881      if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
56882        return;
56883      } // an error occurred from the active pendingSegment_ so reset everything
56884
56885
56886      if (error) {
56887        this.pendingSegment_ = null;
56888        this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
56889
56890        if (error.code === REQUEST_ERRORS.ABORTED) {
56891          return;
56892        }
56893
56894        this.pause(); // the error is really just that at least one of the requests timed-out
56895        // set the bandwidth to a very low value and trigger an ABR switch to
56896        // take emergency action
56897
56898        if (error.code === REQUEST_ERRORS.TIMEOUT) {
56899          this.handleTimeout_();
56900          return;
56901        } // if control-flow has arrived here, then the error is real
56902        // emit an error event to blacklist the current playlist
56903
56904
56905        this.mediaRequestsErrored += 1;
56906        this.error(error);
56907        this.trigger('error');
56908        return;
56909      }
56910
56911      var segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
56912      // generated for ABR purposes
56913
56914      this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
56915      segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
56916
56917      if (result.gopInfo) {
56918        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
56919      } // Although we may have already started appending on progress, we shouldn't switch the
56920      // state away from loading until we are officially done loading the segment data.
56921
56922
56923      this.state = 'APPENDING'; // used for testing
56924
56925      this.trigger('appending');
56926      this.waitForAppendsToComplete_(segmentInfo);
56927    };
56928
56929    _proto.setTimeMapping_ = function setTimeMapping_(timeline) {
56930      var timelineMapping = this.syncController_.mappingForTimeline(timeline);
56931
56932      if (timelineMapping !== null) {
56933        this.timeMapping_ = timelineMapping;
56934      }
56935    };
56936
56937    _proto.updateMediaSecondsLoaded_ = function updateMediaSecondsLoaded_(segment) {
56938      if (typeof segment.start === 'number' && typeof segment.end === 'number') {
56939        this.mediaSecondsLoaded += segment.end - segment.start;
56940      } else {
56941        this.mediaSecondsLoaded += segment.duration;
56942      }
56943    };
56944
56945    _proto.shouldUpdateTransmuxerTimestampOffset_ = function shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
56946      if (timestampOffset === null) {
56947        return false;
56948      } // note that we're potentially using the same timestamp offset for both video and
56949      // audio
56950
56951
56952      if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
56953        return true;
56954      }
56955
56956      if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
56957        return true;
56958      }
56959
56960      return false;
56961    };
56962
56963    _proto.trueSegmentStart_ = function trueSegmentStart_(_ref9) {
56964      var currentStart = _ref9.currentStart,
56965          playlist = _ref9.playlist,
56966          mediaIndex = _ref9.mediaIndex,
56967          firstVideoFrameTimeForData = _ref9.firstVideoFrameTimeForData,
56968          currentVideoTimestampOffset = _ref9.currentVideoTimestampOffset,
56969          useVideoTimingInfo = _ref9.useVideoTimingInfo,
56970          videoTimingInfo = _ref9.videoTimingInfo,
56971          audioTimingInfo = _ref9.audioTimingInfo;
56972
56973      if (typeof currentStart !== 'undefined') {
56974        // if start was set once, keep using it
56975        return currentStart;
56976      }
56977
56978      if (!useVideoTimingInfo) {
56979        return audioTimingInfo.start;
56980      }
56981
56982      var previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame c
56982ontained
56983      // within that segment. Since the transmuxer maintains a cache of incomplete data
56984      // from and/or the last frame seen, the start time may reflect a frame that starts
56985      // in the previous segment. Check for that case and ensure the start time is
56986      // accurate for the segment.
56987
56988      if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
56989        return firstVideoFrameTimeForData;
56990      }
56991
56992      return videoTimingInfo.start;
56993    };
56994
56995    _proto.waitForAppendsToComplete_ = function waitForAppendsToComplete_(segmentInfo) {
56996      var trackInfo = this.getCurrentMediaInfo_(segmentInfo);
56997
56998      if (!trackInfo) {
56999        this.error({
57000          message: 'No starting media returned, likely due to an unsupported media format.',
57001          blacklistDuration: Infinity
57002        });
57003        this.trigger('error');
57004        return;
57005      } // Although transmuxing is done, appends may not yet be finished. Throw a marker
57006      // on each queue this loader is responsible for to ensure that the appends are
57007      // complete.
57008
57009
57010      var hasAudio = trackInfo.hasAudio,
57011          hasVideo = trackInfo.hasVideo,
57012          isMuxed = trackInfo.isMuxed;
57013      var waitForVideo = this.loaderType_ === 'main' && hasVideo;
57014      var waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
57015      segmentInfo.waitingOnAppends = 0; // segments with no data
57016
57017      if (!segmentInfo.hasAppendedData_) {
57018        if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
57019          // When there's no audio or video data in the segment, there's no audio or video
57020          // timing information.
57021          //
57022          // If there's no audio or video timing information, then the timestamp offset
57023          // can't be adjusted to the appropriate value for the transmuxer and source
57024          // buffers.
57025          //
57026          // Therefore, the next segment should be used to set the timestamp offset.
57027          this.isPendingTimestampOffset_ = true;
57028        } // override settings for metadata only segments
57029
57030
57031        segmentInfo.timingInfo = {
57032          start: 0
57033        };
57034        segmentInfo.waitingOnAppends++;
57035
57036        if (!this.isPendingTimestampOffset_) {
57037          // update the timestampoffset
57038          this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
57039          // no video/audio data.
57040
57041          this.processMetadataQueue_();
57042        } // append is "done" instantly with no data.
57043
57044
57045        this.checkAppendsDone_(segmentInfo);
57046        return;
57047      } // Since source updater could call back synchronously, do the increments first.
57048
57049
57050      if (waitForVideo) {
57051        segmentInfo.waitingOnAppends++;
57052      }
57053
57054      if (waitForAudio) {
57055        segmentInfo.waitingOnAppends++;
57056      }
57057
57058      if (waitForVideo) {
57059        this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
57060      }
57061
57062      if (waitForAudio) {
57063        this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
57064      }
57065    };
57066
57067    _proto.checkAppendsDone_ = function checkAppendsDone_(segmentInfo) {
57068      if (this.checkForAbort_(segmentInfo.requestId)) {
57069        return;
57070      }
57071
57072      segmentInfo.waitingOnAppends--;
57073
57074      if (segmentInfo.waitingOnAppends === 0) {
57075        this.handleAppendsDone_();
57076      }
57077    };
57078
57079    _proto.checkForIllegalMediaSwitch = function checkForIllegalMediaSwitch(trackInfo) {
57080      var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
57081
57082      if (illegalMediaSwitchError) {
57083        this.error({
57084          message: illegalMediaSwitchError,
57085          blacklistDuration: Infinity
57086        });
57087        this.trigger('error');
57088        return true;
57089      }
57090
57091      return false;
57092    };
57093
57094    _proto.updateSourceBufferTimestampOffset_ = function updateSourceBufferTimestampOffset_(segmentInfo) {
57095      if (segmentInfo.timestampOffset === null || // we don't yet have the start for whatever media type (video or audio) has
57096      // priority, timing-wise, so we must wait
57097      typeof segmentInfo.timingInfo.start !== 'number' || // already updated the timestamp offset for this segment
57098      segmentInfo.changedTimestampOffset || // the alt audio loader should not be responsible for setting the timestamp offset
57099      this.loaderType_ !== 'main') {
57100        return;
57101      }
57102
57103      var didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
57104      // the timing info here comes from video. In the event that the audio is longer than
57105      // the video, this will trim the start of the audio.
57106      // This also trims any offset from 0 at the beginning of the media
57107
57108      segmentInfo.timestampOffset -= segmentInfo.timingInfo.start; // In the event that there are part segment downloads, each will try to update the
57109      // timestamp offset. Retaining this bit of state prevents us from updating in the
57110      // future (within the same segment), however, there may be a better way to handle it.
57111
57112      segmentInfo.changedTimestampOffset = true;
57113
57114      if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
57115        this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
57116        didChange = true;
57117      }
57118
57119      if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
57120        this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
57121        didChange = true;
57122      }
57123
57124      if (didChange) {
57125        this.trigger('timestampoffset');
57126      }
57127    };
57128
57129    _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_(segmentInfo) {
57130      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
57131      var trackInfo = this.getMediaInfo_();
57132      var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
57133      var prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
57134
57135      if (!prioritizedTimingInfo) {
57136        return;
57137      }
57138
57139      segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ? // End time may not exist in a case where we aren't parsing the full segment (one
57140      // current example is the case of fmp4), so use the rough duration to calculate an
57141      // end time.
57142      prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
57143    }
57144    /**
57145     * callback to run when appendBuffer is finished. detects if we are
57146     * in a good state to do things with the data we got, or if we need
57147     * to wait for more
57148     *
57149     * @private
57150     */
57151    ;
57152
57153    _proto.handleAppendsDone_ = function handleAppendsDone_() {
57154      // appendsdone can cause an abort
57155      if (this.pendingSegment_) {
57156        this.trigger('appendsdone');
57157      }
57158
57159      if (!this.pendingSegment_) {
57160        this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
57161        // all appending cases?
57162
57163        if (!this.paused()) {
57164          this.monitorBuffer_();
57165        }
57166
57167        return;
57168      }
57169
57170      var segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's
57171      // best to wait until all appends are done so we're sure that the primary media is
57172      // finished (and we have its end time).
57173
57174      this.updateTimingInfoEnd_(segmentInfo);
57175
57176      if (this.shouldSaveSegmentTimingInfo_) {
57177        // Timeline mappings should only be saved for the main loader. This is for multiple
57178        // reasons:
57179        //
57180        // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
57181        //    and the main loader try to save the timeline mapping, whichever comes later
57182        //    will overwrite the first. In theory this is OK, as the mappings should be the
57183        //    same, however, it breaks for (2)
57184        // 2) In the event of a live stream, the initial live point will make for a somewhat
57185        //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
57186        //    the mapping will be off for one of the streams, dependent on which one was
57187        //    first saved (see (1)).
57188        // 3) Primary timing goes by video in VHS, so the mapping should be video.
57189        //
57190        // Since the audio loader will wait for the main loader to load the first segment,
57191        // the main loader will save the first timeline mapping, and ensure that there won't
57192        // be a case where audio loads two segments without saving a mapping (thus leading
57193        // to missing segment timing info).
57194        this.syncController_.saveSegmentTimingInfo({
57195          segmentInfo: segmentInfo,
57196          shouldSaveTimelineMapping: this.loaderType_ === 'main'
57197        });
57198      }
57199
57200      var segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
57201
57202      if (segmentDurationMessage) {
57203        if (segmentDurationMessage.severity === 'warn') {
57204          videojs.log.warn(segmentDurationMessage.message);
57205        } else {
57206          this.logger_(segmentDurationMessage.message);
57207        }
57208      }
57209
57210      this.recordThroughput_(segmentInfo);
57211      this.pendingSegment_ = null;
57212      this.state = 'READY';
57213
57214      if (segmentInfo.isSyncRequest) {
57215        this.trigger('syncinfoupdate'); // if the sync request was not appended
57216        // then it was not the correct segment.
57217        // throw it away and use the data it gave us
57218        // to get the correct one.
57219
57220        if (!segmentInfo.hasAppendedData_) {
57221          this.logger_("Throwing away un-appended sync request " + segmentInfoString(segmentInfo));
57222          return;
57223        }
57224      }
57225
57226      this.logger_("Appended " + segmentInfoString(segmentInfo));
57227      this.addSegmentMetadataCue_(segmentInfo);
57228      this.fetchAtBuffer_ = true;
57229
57230      if (this.currentTimeline_ !== segmentInfo.timeline) {
57231        this.timelineChangeController_.lastTimelineChange({
57232          type: this.loaderType_,
57233          from: this.currentTimeline_,
57234          to: segmentInfo.timeline
57235        }); // If audio is not disabled, the main segment loader is responsible for updating
57236        // the audio timeline as well. If the content is video only, this won't have any
57237        // impact.
57238
57239        if (this.loaderType_ === 'main' && !this.audioDisabled_) {
57240          this.timelineChangeController_.lastTimelineChange({
57241            type: 'audio',
57242            from: this.currentTimeline_,
57243            to: segmentInfo.timeline
57244          });
57245        }
57246      }
57247
57248      this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
57249      // the following conditional otherwise it may consider this a bad "guess"
57250      // and attempt to resync when the post-update seekable window and live
57251      // point would mean that this was the perfect segment to fetch
57252
57253      this.trigger('syncinfoupdate');
57254      var segment = segmentInfo.segment; // If we previously appended a segment that ends more than 3 targetDurations before
57255      // the currentTime_ that means that our conservative guess was too conservative.
57256      // In that case, reset the loader state so that we try to use any information gained
57257      // from the previous request to create a new, more accurate, sync-point.
57258
57259      if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
57260        this.resetEverything();
57261        return;
57262      }
57263
57264      var isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
57265      // and conservatively guess
57266
57267      if (isWalkingForward) {
57268        this.trigger('bandwidthupdate');
57269      }
57270
57271      this.trigger('progress');
57272      this.mediaIndex = segmentInfo.mediaIndex;
57273      this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
57274      // buffer, end the stream. this ensures the "ended" event will
57275      // fire if playback reaches that point.
57276
57277      if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
57278        this.endOfStream();
57279      } // used for testing
57280
57281
57282      this.trigger('appended');
57283
57284      if (segmentInfo.hasAppendedData_) {
57285        this.mediaAppends++;
57286      }
57287
57288      if (!this.paused()) {
57289        this.monitorBuffer_();
57290      }
57291    }
57292    /**
57293     * Records the current throughput of the decrypt, transmux, and append
57294     * portion of the semgment pipeline. `throughput.rate` is a the cumulative
57295     * moving average of the throughput. `throughput.count` is the number of
57296     * data points in the average.
57297     *
57298     * @private
57299     * @param {Object} segmentInfo the object returned by loadSegment
57300     */
57301    ;
57302
57303    _proto.recordThroughput_ = function recordThroughput_(segmentInfo) {
57304      if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
57305        this.logger_("Ignoring segment's throughput because its duration of " + segmentInfo.duration + (" is less than the min to record " + MIN_SEGMENT_DURATION_TO_SAVE_STATS));
57306        return;
57307      }
57308
57309      var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
57310      // by zero in the case where the throughput is ridiculously high
57311
57312      var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
57313
57314      var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
57315      //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
57316
57317      this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
57318    }
57319    /**
57320     * Adds a cue to the segment-metadata track with some metadata information about the
57321     * segment
57322     *
57323     * @private
57324     * @param {Object} segmentInfo
57325     *        the object returned by loadSegment
57326     * @method addSegmentMetadataCue_
57327     */
57328    ;
57329
57330    _proto.addSegmentMetadataCue_ = function addSegmentMetadataCue_(segmentInfo) {
57331      if (!this.segmentMetadataTrack_) {
57332        return;
57333      }
57334
57335      var segment = segmentInfo.segment;
57336      var start = segment.start;
57337      var end = segment.end;
57337 // Do not try adding the cue if the start and end times are invalid.
57338
57339      if (!finite(start) || !finite(end)) {
57340        return;
57341      }
57342
57343      removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
57344      var Cue = window_1.WebKitDataCue || window_1.VTTCue;
57345      var value = {
57346        custom: segment.custom,
57347        dateTimeObject: segment.dateTimeObject,
57348        dateTimeString: segment.dateTimeString,
57349        bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
57350        resolution: segmentInfo.playlist.attributes.RESOLUTION,
57351        codecs: segmentInfo.playlist.attributes.CODECS,
57352        byteLength: segmentInfo.byteLength,
57353        uri: segmentInfo.uri,
57354        timeline: segmentInfo.timeline,
57355        playlist: segmentInfo.playlist.id,
57356        start: start,
57357        end: end
57358      };
57359      var data = JSON.stringify(value);
57360      var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
57361      // the differences of WebKitDataCue in safari and VTTCue in other browsers
57362
57363      cue.value = value;
57364      this.segmentMetadataTrack_.addCue(cue);
57365    };
57366
57367    return SegmentLoader;
57368  }(videojs.EventTarget);
57369
57370  function noop() {}
57371
57372  var toTitleCase = function toTitleCase(string) {
57373    if (typeof string !== 'string') {
57374      return string;
57375    }
57376
57377    return string.replace(/./, function (w) {
57378      return w.toUpperCase();
57379    });
57380  };
57381
57382  var bufferTypes = ['video', 'audio'];
57383
57384  var _updating = function updating(type, sourceUpdater) {
57385    var sourceBuffer = sourceUpdater[type + "Buffer"];
57386    return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
57387  };
57388
57389  var nextQueueIndexOfType = function nextQueueIndexOfType(type, queue) {
57390    for (var i = 0; i < queue.length; i++) {
57391      var queueEntry = queue[i];
57392
57393      if (queueEntry.type === 'mediaSource') {
57394        // If the next entry is a media source entry (uses multiple source buffers), block
57395        // processing to allow it to go through first.
57396        return null;
57397      }
57398
57399      if (queueEntry.type === type) {
57400        return i;
57401      }
57402    }
57403
57404    return null;
57405  };
57406
57407  var shiftQueue = function shiftQueue(type, sourceUpdater) {
57408    if (sourceUpdater.queue.length === 0) {
57409      return;
57410    }
57411
57412    var queueIndex = 0;
57413    var queueEntry = sourceUpdater.queue[queueIndex];
57414
57415    if (queueEntry.type === 'mediaSource') {
57416      if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
57417        sourceUpdater.queue.shift();
57418        queueEntry.action(sourceUpdater);
57419
57420        if (queueEntry.doneFn) {
57421          queueEntry.doneFn();
57422        } // Only specific source buffer actions must wait for async updateend events. Media
57423        // Source actions process synchronously. Therefore, both audio and video source
57424        // buffers are now clear to process the next queue entries.
57425
57426
57427        shiftQueue('audio', sourceUpdater);
57428        shiftQueue('video', sourceUpdater);
57429      } // Media Source actions require both source buffers, so if the media source action
57430      // couldn't process yet (because one or both source buffers are busy), block other
57431      // queue actions until both are available and the media source action can process.
57432
57433
57434      return;
57435    }
57436
57437    if (type === 'mediaSource') {
57438      // If the queue was shifted by a media source action (this happens when pushing a
57439      // media source action onto the queue), then it wasn't from an updateend event from an
57440      // audio or video source buffer, so there's no change from previous state, and no
57441      // processing should be done.
57442      return;
57443    } // Media source queue entries don't need to consider whether the source updater is
57444    // started (i.e., source buffers are created) as they don't need the source buffers, but
57445    // source buffer queue entries do.
57446
57447
57448    if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || _updating(type, sourceUpdater)) {
57449      return;
57450    }
57451
57452    if (queueEntry.type !== type) {
57453      queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
57454
57455      if (queueIndex === null) {
57456        // Either there's no queue entry that uses this source buffer type in the queue, or
57457        // there's a media source queue entry before the next entry of this type, in which
57458        // case wait for that action to process first.
57459        return;
57460      }
57461
57462      queueEntry = sourceUpdater.queue[queueIndex];
57463    }
57464
57465    sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
57466    //
57467    // The queue pending operation must be set before the action is performed in the event
57468    // that the action results in a synchronous event that is acted upon. For instance, if
57469    // an exception is thrown that can be handled, it's possible that new actions will be
57470    // appended to an empty queue and immediately executed, but would not have the correct
57471    // pending information if this property was set after the action was performed.
57472
57473    sourceUpdater.queuePending[type] = queueEntry;
57474    queueEntry.action(type, sourceUpdater);
57475
57476    if (!queueEntry.doneFn) {
57477      // synchronous operation, process next entry
57478      sourceUpdater.queuePending[type] = null;
57479      shiftQueue(type, sourceUpdater);
57480      return;
57481    }
57482  };
57483
57484  var cleanupBuffer = function cleanupBuffer(type, sourceUpdater) {
57485    var buffer = sourceUpdater[type + "Buffer"];
57486    var titleType = toTitleCase(type);
57487
57488    if (!buffer) {
57489      return;
57490    }
57491
57492    buffer.removeEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
57493    buffer.removeEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
57494    sourceUpdater.codecs[type] = null;
57495    sourceUpdater[type + "Buffer"] = null;
57496  };
57497
57498  var inSourceBuffers = function inSourceBuffers(mediaSource, sourceBuffer) {
57499    return mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
57500  };
57501
57502  var actions = {
57503    appendBuffer: function appendBuffer(bytes, segmentInfo, onError) {
57504      return function (type, sourceUpdater) {
57505        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
57506        // or the media source does not contain this source buffer.
57507
57508        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57509          return;
57510        }
57511
57512        sourceUpdater.logger_("Appending segment " + segmentInfo.mediaIndex + "'s " + bytes.length + " bytes to " + type + "Buffer");
57513
57514        try {
57515          sourceBuffer.appendBuffer(bytes);
57516        } catch (e) {
57517          sourceUpdater.logger_("Error with code " + e.code + " " + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + ("when appending segment " + segmentInfo.mediaIndex + " to " + type + "Buffer"));
57518          sourceUpdater.queuePending[type] = null;
57519          onError(e);
57520        }
57521      };
57522    },
57523    remove: function remove(start, end) {
57524      return function (type, sourceUpdater) {
57525        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
57526        // or the media source does not contain this source buffer.
57527
57528        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57529          return;
57530        }
57531
57532        sourceUpdater.logger_("Removing " + start + " to " + end + " from " + type + "Buffer");
57533
57534        try {
57535          sourceBuffer.remove(start, end);
57536        } catch (e) {
57537          sourceUpdater.logger_("Remove " + start + " to " + end + " from " + type + "Buffer failed");
57538        }
57539      };
57540    },
57541    timestampOffset: function timestampOffset(offset) {
57542      return function (type, sourceUpdater) {
57543        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
57544        // or the media source does not contain this source buffer.
57545
57546        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57547          return;
57548        }
57549
57550        sourceUpdater.logger_("Setting " + type + "timestampOffset to " + offset);
57551        sourceBuffer.timestampOffset = offset;
57552      };
57553    },
57554    callback: function callback(_callback) {
57555      return function (type, sourceUpdater) {
57556        _callback();
57557      };
57558    },
57559    endOfStream: function endOfStream(error) {
57560      return function (sourceUpdater) {
57561        if (sourceUpdater.mediaSource.readyState !== 'open') {
57562          return;
57563        }
57564
57565        sourceUpdater.logger_("Calling mediaSource endOfStream(" + (error || '') + ")");
57566
57567        try {
57568          sourceUpdater.mediaSource.endOfStream(error);
57569        } catch (e) {
57570          videojs.log.warn('Failed to call media source endOfStream', e);
57571        }
57572      };
57573    },
57574    duration: function duration(_duration) {
57575      return function (sourceUpdater) {
57576        sourceUpdater.logger_("Setting mediaSource duration to " + _duration);
57577
57578        try {
57579          sourceUpdater.mediaSource.duration = _duration;
57580        } catch (e) {
57581          videojs.log.warn('Failed to set media source duration', e);
57582        }
57583      };
57584    },
57585    abort: function abort() {
57586      return function (type, sourceUpdater) {
57587        if (sourceUpdater.mediaSource.readyState !== 'open') {
57588          return;
57589        }
57590
57591        var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
57592        // or the media source does not contain this source buffer.
57593
57594        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57595          return;
57596        }
57597
57598        sourceUpdater.logger_("calling abort on " + type + "Buffer");
57599
57600        try {
57601          sourceBuffer.abort();
57602        } catch (e) {
57603          videojs.log.warn("Failed to abort on " + type + "Buffer", e);
57604        }
57605      };
57606    },
57607    addSourceBuffer: function addSourceBuffer(type, codec) {
57608      return function (sourceUpdater) {
57609        var titleType = toTitleCase(type);
57610        var mime = getMimeForCodec(codec);
57611        sourceUpdater.logger_("Adding " + type + "Buffer with codec " + codec + " to mediaSource");
57612        var sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
57613        sourceBuffer.addEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
57614        sourceBuffer.addEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
57615        sourceUpdater.codecs[type] = codec;
57616        sourceUpdater[type + "Buffer"] = sourceBuffer;
57617      };
57618    },
57619    removeSourceBuffer: function removeSourceBuffer(type) {
57620      return function (sourceUpdater) {
57621        var sourceBuffer = sourceUpdater[type + "Buffer"];
57622        cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
57623        // or the media source does not contain this source buffer.
57624
57625        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57626          return;
57627        }
57628
57629        sourceUpdater.logger_("Removing " + type + "Buffer with codec " + sourceUpdater.codecs[type] + " from mediaSource");
57630
57631        try {
57632          sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
57633        } catch (e) {
57634          videojs.log.warn("Failed to removeSourceBuffer " + type + "Buffer", e);
57635        }
57636      };
57637    },
57638    changeType: function changeType(codec) {
57639      return function (type, sourceUpdater) {
57640        var sourceBuffer = sourceUpdater[type + "Buffer"];
57641        var mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
57642        // or the media source does not contain this source buffer.
57643
57644        if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
57645          return;
57646        } // do not update codec if we don't need to.
57647
57648
57649        if (sourceUpdater.codecs[type] === codec) {
57650          return;
57651        }
57652
57653        sourceUpdater.logger_("changing " + type + "Buffer codec from " + sourceUpdater.codecs[type] + " to " + codec);
57654        sourceBuffer.changeType(mime);
57655        sourceUpdater.codecs[type] = codec;
57656      };
57657    }
57658  };
57659
57660  var pushQueue = function pushQueue(_ref) {
57661    var type = _ref.type,
57662        sourceUpdater = _ref.sourceUpdater,
57663        action = _ref.action,
57664        doneFn = _ref.doneFn,
57665        name = _ref.name;
57666    sourceUpdater.queue.push({
57667      type: type,
57668      action: action,
57669      doneFn: doneFn,
57670      name: name
57671    });
57672    shiftQueue(type, sourceUpdater);
57673  };
57674
57675  var onUpdateend = function onUpdateend(type, sourceUpdater) {
57676    return function (e) {
57677      // Although there should, in theory, be a pending action for any updateend receieved,
57678      // there are some actions that may trigger updateend events without set definitions in
57679      // the w3c spec. For instance, setting the duration on the media source may trigger
57680      // updateend events on source buffers. This does not appear to be in the spec. As such,
57681      // if we encounter an updateend without a corresponding pending action from our queue
57682      // for that source buffer type, process the next action.
57683      if (sourceUpdater.queuePending[type]) {
57684        var doneFn = sourceUpdater.queuePending[type].doneFn;
57685        sourceUpdater.queuePending[type] = null;
57686
57687        if (doneFn) {
57688          // if there's an error, report it
57689          doneFn(sourceUpdater[type + "Error_"]);
57690        }
57691      }
57692
57693      shiftQueue(type, sourceUpdater);
57694    };
57695  };
57696  /**
57697   * A queue of callbacks to be serialized and applied when a
57698   * MediaSource and its associated SourceBuffers are not in the
57699   * updating state. It is used by the segment loader to update the
57700   * underlying SourceBuffers when new data is loaded, for instance.
57701   *
57702   * @class SourceUpdater
57703   * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
57704   * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
57705   */
57706
57707
57708  var SourceUpdater = /*#__PURE__*/function (_videojs$EventTarget) {
57709    inheritsLoose(SourceUpdater, _videojs$EventTarget);
57710
57711    function SourceUpdater(mediaSource) {
57712      var _this;
57713
57714      _this = _videojs$EventTarget.call(this) || this;
57715      _this.mediaSource = mediaSource;
57716
57717      _this.sourceopenListener_ = function () {
57718        return shiftQueue('mediaSource', assertThisInitialized(_this));
57719      };
57720
57721      _this.mediaSource.addEventListener('sourceopen', _this.sourceopenListener_);
57722
57723      _this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
57724
57725      _this.audioTimestampOffset_ = 0;
57726      _this.videoTimestampOffset_ = 0;
57727      _this.queue = [];
57728      _this.queuePending = {
57729        audio: null,
57730        video: null
57731      };
57732      _this.delayedAudioAppendQueue_ = [];
57733      _this.videoAppendQueued_ = false;
57734      _this.codecs = {};
57735      _this.onVideoUpdateEnd_ = onUpdateend('video', assertThisInitialized(_this));
57736      _this.onAudioUpdateEnd_ = onUpdateend('audio', assertThisInitialized(_this));
57737
57738      _this.onVideoError_ = function (e) {
57739        // used for debugging
57740        _this.videoError_ = e;
57741      };
57742
57743      _this.onAudioError_ = function (e) {
57744        // used for debugging
57745        _this.audioError_ = e;
57746      };
57747
57748      _this.createdSourceBuffers_ = false;
57749      _this.initializedEme_ = false;
57750      _this.triggeredReady_ = false;
57751      return _this;
57752    }
57753
57754    var _proto = SourceUpdater.prototype;
57755
57756    _proto.initializedEme = function initializedEme() {
57757      this.initializedEme_ = true;
57758      this.triggerReady();
57759    };
57760
57761    _proto.hasCreatedSourceBuffers = function hasCreatedSourceBuffers() {
57762      // if false, likely waiting on one of the segment loaders to get enough data to create
57763      // source buffers
57764      return this.createdSourceBuffers_;
57765    };
57766
57767    _proto.hasInitializedAnyEme = function hasInitializedAnyEme() {
57768      return this.initializedEme_;
57769    };
57770
57771    _proto.ready = function ready() {
57772      return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
57773    };
57774
57775    _proto.createSourceBuffers = function createSourceBuffers(codecs) {
57776      if (this.hasCreatedSourceBuffers()) {
57777        // already created them before
57778        return;
57779      } // the intial addOrChangeSourceBuffers will always be
57780      // two add buffers.
57781
57782
57783      this.addOrChangeSourceBuffers(codecs);
57784      this.createdSourceBuffers_ = true;
57785      this.trigger('createdsourcebuffers');
57786      this.triggerReady();
57787    };
57788
57789    _proto.triggerReady = function triggerReady() {
57790      // only allow ready to be triggered once, this prevents the case
57791      // where:
57792      // 1. we trigger createdsourcebuffers
57793      // 2. ie 11 synchronously initializates eme
57794      // 3. the synchronous initialization causes us to trigger ready
57795      // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
57796      if (this.ready() && !this.triggeredReady_) {
57797        this.triggeredReady_ = true;
57798        this.trigger('ready');
57799      }
57800    }
57801    /**
57802     * Add a type of source buffer to the media source.
57803     *
57804     * @param {string} type
57805     *        The type of source buffer to add.
57806     *
57807     * @param {string} codec
57808     *        The codec to add the source buffer with.
57809     */
57810    ;
57811
57812    _proto.addSourceBuffer = function addSourceBuffer(type, codec) {
57813      pushQueue({
57814        type: 'mediaSource',
57815        sourceUpdater: this,
57816        action: actions.addSourceBuffer(type, codec),
57817        name: 'addSourceBuffer'
57818      });
57819    }
57820    /**
57821     * call abort on a source buffer.
57822     *
57823     * @param {string} type
57824     *        The type of source buffer to call abort on.
57825     */
57826    ;
57827
57828    _proto.abort = function abort(type) {
57829      pushQueue({
57830        type: type,
57831        sourceUpdater: this,
57832        action: actions.abort(type),
57833        name: 'abort'
57834      });
57835    }
57836    /**
57837     * Call removeSourceBuffer and remove a specific type
57838     * of source buffer on the mediaSource.
57839     *
57840     * @param {string} type
57841     *        The type of source buffer to remove.
57842     */
57843    ;
57844
57845    _proto.removeSourceBuffer = function removeSourceBuffer(type) {
57846      if (!this.canRemoveSourceBuffer()) {
57847        videojs.log.error('removeSourceBuffer is not supported!');
57848        return;
57849      }
57850
57851      pushQueue({
57852        type: 'mediaSource',
57853        sourceUpdater: this,
57854        action: actions.removeSourceBuffer(type),
57855        name: 'removeSourceBuffer'
57856      });
57857    }
57858    /**
57859     * Whether or not the removeSourceBuffer function is supported
57860     * on the mediaSource.
57861     *
57862     * @return {boolean}
57863     *          if removeSourceBuffer can be called.
57864     */
57865    ;
57866
57867    _proto.canRemoveSourceBuffer = function canRemoveSourceBuffer() {
57868      // IE reports that it supports removeSourceBuffer, but often throws
57869      // errors when attempting to use the function. So we report that it
57870      // does not support removeSourceBuffer. As of Firefox 83 removeSourceBuffer
57871      // throws errors, so we report that it does not support this as well.
57872      return !videojs.browser.IE_VERSION && !videojs.browser.IS_FIREFOX && window_1.MediaSource && window_1.MediaSource.prototype && typeof window_1.MediaSource.prototype.removeSourceBuffer === 'function';
57873    }
57874    /**
57875     * Whether or not the changeType function is supported
57876     * on our SourceBuffers.
57877     *
57878     * @return {boolean}
57879     *         if changeType can be called.
57880     */
57881    ;
57882
57883    SourceUpdater.canChangeType = function canChangeType() {
57884      return window_1.SourceBuffer && window_1.SourceBuffer.prototype && typeof window_1.SourceBuffer.prototype.changeType === 'function';
57885    }
57886    /**
57887     * Whether or not the changeType function is supported
57888     * on our SourceBuffers.
57889     *
57890     * @return {boolean}
57891     *         if changeType can be called.
57892     */
57893    ;
57894
57895    _proto.canChangeType = function canChangeType() {
57896      return this.constructor.canChangeType();
57897    }
57898    /**
57899     * Call the changeType function on a source buffer, given the code and type.
57900     *
57901     * @param {string} type
57902     *        The type of source buffer to call changeType on.
57903     *
57904     * @param {string} codec
57905     *        The codec string to change type with on the source buffer.
57906     */
57907    ;
57908
57909    _proto.changeType = function changeType(type, codec) {
57910      if (!this.canChangeType()) {
57911        videojs.log.error('changeType is not supported!');
57912        return;
57913      }
57914
57915      pushQueue({
57916        type: type,
57917        sourceUpdater: this,
57918        action: actions.changeType(codec),
57919        name: 'changeType'
57920      });
57921    }
57922    /**
57923     * Add source buffers with a codec or, if they are already created,
57924     * call changeType on source buffers using changeType.
57925     *
57926     * @param {Object} codecs
57927     *        Codecs to switch to
57928     */
57929    ;
57930
57931    _proto.addOrChangeSourceBuffers = function addOrChangeSourceBuffers(codecs) {
57932      var _this2 = this;
57933
57934      if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
57935        throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
57936      }
57937
57938      Object.keys(codecs).forEach(function (type) {
57939        var codec = codecs[type];
57940
57941        if (!_this2.hasCreatedSourceBuffers()) {
57942          return _this2.addSourceBuffer(type, codec);
57943        }
57944
57945        if (_this2.canChangeType()) {
57946          _this2.changeType(type, codec);
57947        }
57948      });
57949    }
57950    /**
57951     * Queue an update to append an ArrayBuffer.
57952     *
57953     * @param {MediaObject} object containing audioBytes and/or videoBytes
57954     * @param {Function} done the function to call when done
57955     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
57956     */
57957    ;
57958
57959    _proto.appendBuffer = function appendBuffer(options, doneFn) {
57960      var _this3 = this;
57961
57962      var segmentInfo = options.segmentInfo,
57963          type = options.type,
57964          bytes = options.bytes;
57965      this.processedAppend_ = true;
57966
57967      if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
57968        this.delayedAudioAppendQueue_.push([options, doneFn]);
57969        this.logger_("delayed audio append of " + bytes.length + " until video append");
57970        return;
57971      } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
57972      // not be fired. This means that the queue will be blocked until the next action
57973      // taken by the segment-loader. Provide a mechanism for segment-loader to handle
57974      // these errors by calling the doneFn with the specific error.
57975
57976
57977      var onError = doneFn;
57978      pushQueue({
57979        type: type,
57980        sourceUpdater: this,
57981        action: actions.appendBuffer(bytes, segmentInfo || {
57982          mediaIndex: -1
57983        }, onError),
57984        doneFn: doneFn,
57985        name: 'appendBuffer'
57986      });
57987
57988      if (type === 'video') {
57989        this.videoAppendQueued_ = true;
57990
57991        if (!this.delayedAudioAppendQueue_.length) {
57992          return;
57993        }
57994
57995        var queue = this.delayedAudioAppendQueue_.slice();
57996        this.logger_("queuing delayed audio " + queue.length + " appendBuffers");
57997        this.delayedAudioAppendQueue_.length = 0;
57998        queue.forEach(function (que) {
57999          _this3.appendBuffer.apply(_this3, que);
58000        });
58001      }
58002    }
58003    /**
58004     * Get the audio buffer's buffered timerange.
58005     *
58006     * @return {TimeRange}
58007     *         The audio buffer's buffered time range
58008     */
58009    ;
58010
58011    _proto.audioBuffered = function audioBuffered() {
58012      // no media source/source buffer or it isn't in the media sources
58013      // source buffer list
58014      if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
58015        return videojs.createTimeRange();
58016      }
58017
58018      return this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs.createTimeRange();
58019    }
58020    /**
58021     * Get the video buffer's buffered timerange.
58022     *
58023     * @return {TimeRange}
58024     *         The video buffer's buffered time range
58025     */
58026    ;
58027
58028    _proto.videoBuffered = function videoBuffered() {
58029      // no media source/source buffer or it isn't in the media sources
58030      // source buffer list
58031      if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
58032        return videojs.createTimeRange();
58033      }
58034
58035      return this.videoBuffer.buffered ? this.videoBuffer.buffered : videojs.createTimeRange();
58036    }
58037    /**
58038     * Get a combined video/audio buffer's buffered timerange.
58039     *
58040     * @return {TimeRange}
58041     *         the combined time range
58042     */
58043    ;
58044
58045    _proto.buffered = function buffered() {
58046      var video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
58047      var audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
58048
58049      if (audio && !video) {
58050        return this.audioBuffered();
58051      }
58052
58053      if (video && !audio) {
58054        return this.videoBuffered();
58055      }
58056
58057      return bufferIntersection(this.audioBuffered(), this.videoBuffered());
58058    }
58059    /**
58060     * Add a callback to the queue that will set duration on the mediaSource.
58061     *
58062     * @param {number} duration
58063     *        The duration to set
58064     *
58065     * @param {Function} [doneFn]
58066     *        function to run after duration has been set.
58067     */
58068    ;
58069
58070    _proto.setDuration = function setDuration(duration, doneFn) {
58071      if (doneFn === void 0) {
58072        doneFn = noop;
58073      } // In order to set the duration on the media source, it's necessary to wait for all
58074      // source buffers to no longer be updating. "If the updating attribute equals true on
58075      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
58076      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
58077
58078
58079      pushQueue({
58080        type: 'mediaSource',
58081        sourceUpdater: this,
58082        action: actions.duration(duration),
58083        name: 'duration',
58084        doneFn: doneFn
58085      });
58086    }
58087    /**
58088     * Add a mediaSource endOfStream call to the queue
58089     *
58090     * @param {Error} [error]
58091     *        Call endOfStream with an error
58092     *
58093     * @param {Function} [doneFn]
58094     *        A function that should be called when the
58095     *        endOfStream call has finished.
58096     */
58097    ;
58098
58099    _proto.endOfStream = function endOfStream(error, doneFn) {
58100      if (error === void 0) {
58101        error = null;
58102      }
58103
58104      if (doneFn === void 0) {
58105        doneFn = noop;
58106      }
58107
58108      if (typeof error !== 'string') {
58109        error = undefined;
58110      } // In order to set the duration on the media source, it's necessary to wait for all
58111      // source buffers to no longer be updating. "If the updating attribute equals true on
58112      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
58113      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
58114
58115
58116      pushQueue({
58117        type: 'mediaSource',
58118        sourceUpdater: this,
58119        action: actions.endOfStream(error),
58120        name: 'endOfStream',
58121        doneFn: doneFn
58122      });
58123    }
58124    /**
58125     * Queue an update to remove a time range from the buffer.
58126     *
58127     * @param {number} start where to start the removal
58128     * @param {number} end where to end the removal
58129     * @param {Function} [done=noop] optional callback to be executed when the remove
58130     * operation is complete
58131     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
58132     */
58133    ;
58134
58135    _proto.removeAudio = function removeAudio(start, end, done) {
58136      if (done === void 0) {
58137        done = noop;
58138      }
58139
58140      if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
58141        done();
58142        return;
58143      }
58144
58145      pushQueue({
58146        type: 'audio',
58147        sourceUpdater: this,
58148        action: actions.remove(start, end),
58149        doneFn: done,
58150        name: 'remove'
58151      });
58152    }
58153    /**
58154     * Queue an update to remove a time range from the buffer.
58155     *
58156     * @param {number} start where to start the removal
58157     * @param {number} end where to end the removal
58158     * @param {Function} [done=noop] optional callback to be executed when the remove
58159     * operation is complete
58160     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
58161     */
58162    ;
58163
58164    _proto.removeVideo = function removeVideo(start, end, done) {
58165      if (done === void 0) {
58166        done = noop;
58167      }
58168
58169      if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
58170        done();
58171        return;
58172      }
58173
58174      pushQueue({
58175        type: 'video',
58176        sourceUpdater: this,
58177        action: actions.remove(start, end),
58178        doneFn: done,
58179        name: 'remove'
58180      });
58181    }
58182    /**
58183     * Whether the underlying sourceBuffer is updating or not
58184     *
58185     * @return {boolean} the updating status of the SourceBuffer
58186     */
58187    ;
58188
58189    _proto.updating = function updating() {
58190      // the audio/video source buffer is updating
58191      if (_updating('audio', this) || _updating('video', this)) {
58192        return true;
58193      }
58194
58195      return false;
58196    }
58197    /**
58198     * Set/get the timestampoffset on the audio SourceBuffer
58199     *
58200     * @return {number} the timestamp offset
58201     */
58202    ;
58203
58204    _proto.audioTimestampOffset = function audioTimestampOffset(offset) {
58205      if (typeof offset !== 'undefined' && this.audioBuffer && // no point in updating if it's the same
58206      this.audioTimestampOffset_ !== offset) {
58207        pushQueue({
58208          type: 'audio',
58209          sourceUpdater: this,
58210          action: actions.timestampOffset(offset),
58211          name: 'timestampOffset'
58212        });
58213        this.audioTimestampOffset_ = offset;
58214      }
58215
58216      return this.audioTimestampOffset_;
58217    }
58218    /**
58219     * Set/get the timestampoffset on the video SourceBuffer
58220     *
58221     * @return {number} the timestamp offset
58222     */
58223    ;
58224
58225    _proto.videoTimestampOffset = function videoTimestampOffset(offset) {
58226      if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same
58227      this.videoTimestampOffset !== offset) {
58228        pushQueue({
58229          type: 'video',
58230          sourceUpdater: this,
58231          action: actions.timestampOffset(offset),
58232          name: 'timestampOffset'
58233        });
58234        this.videoTimestampOffset_ = offset;
58235      }
58236
58237      return this.videoTimestampOffset_;
58238    }
58239    /**
58240     * Add a function to the queue that will be called
58241     * when it is its turn to run in the audio queue.
58242     *
58243     * @param {Function} callback
58244     *        The callback to queue.
58245     */
58246    ;
58247
58248    _proto.audioQueueCallback = function audioQueueCallback(callback) {
58249      if (!this.audioBuffer) {
58250        return;
58251      }
58252
58253      pushQueue({
58254        type: 'audio',
58255        sourceUpdater: this,
58256        action: actions.callback(callback),
58257        name: 'callback'
58258      });
58259    }
58260    /**
58261     * Add a function to the queue that will be called
58262     * when it is its turn to run in the video queue.
58263     *
58264     * @param {Function} callback
58265     *        The callback to queue.
58266     */
58267    ;
58268
58269    _proto.videoQueueCallback = function videoQueueCallback(callback) {
58270      if (!this.videoBuffer) {
58271        return;
58272      }
58273
58274      pushQueue({
58275        type: 'video',
58276        sourceUpdater: this,
58277        action: actions.callback(callback),
58278        name: 'callback'
58279      });
58280    }
58281    /**
58282     * dispose of the source updater and the underlying sourceBuffer
58283     */
58284    ;
58285
58286    _proto.dispose = function dispose() {
58287      var _this4 = this;
58288
58289      this.trigger('dispose');
58290      bufferTypes.forEach(function (type) {
58291        _this4.abort(type);
58292
58293        if (_this4.canRemoveSourceBuffer()) {
58294          _this4.removeSourceBuffer(type);
58295        } else {
58296          _this4[type + "QueueCallback"](function () {
58297            return cleanupBuffer(type, _this4);
58298          });
58299        }
58300      });
58301      this.videoAppendQueued_ = false;
58302      this.delayedAudioAppendQueue_.length = 0;
58303
58304      if (this.sourceopenListener_) {
58305        this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
58306      }
58307
58308      this.off();
58309    };
58310
58311    return SourceUpdater;
58312  }(videojs.EventTarget);
58313
58314  var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
58315    return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
58316  };
58317
58318  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char3) {
58319    return _char3.charCodeAt(0);
58320  }));
58321  /**
58322   * An object that manages segment loading and appending.
58323   *
58324   * @class VTTSegmentLoader
58325   * @param {Object} options required and optional options
58326   * @extends videojs.EventTarget
58327   */
58328
58329  var VTTSegmentLoader = /*#__PURE__*/function (_SegmentLoader) {
58330    inheritsLoose(VTTSegmentLoader, _SegmentLoader);
58331
58332    function VTTSegmentLoader(settings, options) {
58333      var _this;
58334
58335      if (options === void 0) {
58336        options = {};
58337      }
58338
58339      _this = _SegmentLoader.call(this, settings, options) || this; // SegmentLoader requires a MediaSource be specified or it will throw an error;
58340      // however, VTTSegmentLoader has no need of a media source, so delete the reference
58341
58342      _this.mediaSource_ = null;
58343      _this.subtitlesTrack_ = null;
58344      _this.loaderType_ = 'subtitle';
58345      _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
58346      // the sync controller leads to improper behavior.
58347
58348      _this.shouldSaveSegmentTimingInfo_ = false;
58349      return _this;
58350    }
58351
58352    var _proto = VTTSegmentLoader.prototype;
58353
58354    _proto.createTransmuxer_ = function createTransmuxer_() {
58355      // don't need to transmux any subtitles
58356      return null;
58357    }
58358    /**
58359     * Indicates which time ranges are buffered
58360     *
58361     * @return {TimeRange}
58362     *         TimeRange object representing the current buffered ranges
58363     */
58364    ;
58365
58366    _proto.buffered_ = function buffered_() {
58367      if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
58368        return videojs.createTimeRanges();
58369      }
58370
58371      var cues = this.subtitlesTrack_.cues;
58372      var start = cues[0].startTime;
58373      var end = cues[cues.length - 1].startTime;
58374      return videojs.createTimeRanges([[start, end]]);
58375    }
58376    /**
58377     * Gets and sets init segment for the provided map
58378     *
58379     * @param {Object} map
58380     *        The map object representing the init segment to get or set
58381     * @param {boolean=} set
58382     *        If true, the init segment for the provided map should be saved
58383     * @return {Object}
58384     *         map object for desired init segment
58385     */
58386    ;
58387
58388    _proto.initSegmentForMap = function initSegmentForMap(map, set) {
58389      if (set === void 0) {
58390        set = false;
58391      }
58392
58393      if (!map) {
58394        return null;
58395      }
58396
58397      var id = initSegmentId(map);
58398      var storedMap = this.initSegments_[id];
58399
58400      if (set && !storedMap && map.bytes) {
58401        // append WebVTT line terminators to the media initialization segment if it exists
58402        // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
58403        // requires two or more WebVTT line terminators between the WebVTT header and the
58404        // rest of the file
58405        var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
58406        var combinedSegment = new Uint8Array(combinedByteLength);
58407        combinedSegment.set(map.bytes);
58408        combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
58409        this.initSegments_[id] = storedMap = {
58410          resolvedUri: map.resolvedUri,
58411          byterange: map.byterange,
58412          bytes: combinedSegment
58413        };
58414      }
58415
58416      return storedMap || map;
58417    }
58418    /**
58419     * Returns true if all configuration required for loading is present, otherwise false.
58420     *
58421     * @return {boolean} True if the all configuration is ready for loading
58422     * @private
58423     */
58424    ;
58425
58426    _proto.couldBeginLoading_ = function couldBeginLoading_() {
58427      return this.playlist_ && this.subtitlesTrack_ && !this.paused();
58428    }
58429    /**
58430     * Once all the starting parameters have been specified, begin
58431     * operation. This method should only be invoked from the INIT
58432     * state.
58433     *
58434     * @private
58435     */
58436    ;
58437
58438    _proto.init_ = function init_() {
58439      this.state = 'READY';
58440      this.resetEverything();
58441      return this.monitorBuffer_();
58442    }
58443    /**
58444     * Set a subtitle track on the segment loader to add subtitles to
58445     *
58446     * @param {TextTrack=} track
58447     *        The text track to add loaded subtitles to
58448     * @return {TextTrack}
58449     *        Returns the subtitles track
58450     */
58451    ;
58452
58453    _proto.track = function track(_track) {
58454      if (typeof _track === 'undefined') {
58455        return this.subtitlesTrack_;
58456      }
58457
58458      this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
58459      // buffering now
58460
58461      if (this.state === 'INIT' && this.couldBeginLoading_()) {
58462        this.init_();
58463      }
58464
58465      return this.subtitlesTrack_;
58466    }
58467    /**
58468     * Remove any data in the source buffer between start and end times
58469     *
58470     * @param {number} start - the start time of the region to remove from the buffer
58471     * @param {number} end - the end time of the region to remove from the buffer
58472     */
58473    ;
58474
58475    _proto.remove = function remove(start, end) {
58476      removeCuesFromTrack(start, end, this.subtitlesTrack_);
58477    }
58478    /**
58479     * fill the buffer with segements unless the sourceBuffers are
58480     * currently updating
58481     *
58482     * Note: this function should only ever be called by monitorBuffer_
58483     * and never directly
58484     *
58485     * @private
58486     */
58487    ;
58488
58489    _proto.fillBuffer_ = function fillBuffer_() {
58490      var _this2 = this; // see if we need to begin loading immediately
58491
58492
58493      var segmentInfo = this.chooseNextRequest_();
58494
58495      if (!segmentInfo) {
58496        return;
58497      }
58498
58499      if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
58500        // We don't have the timestamp offset that we need to sync subtitles.
58501        // Rerun on a timestamp offset or user interaction.
58502        var checkTimestampOffset = function checkTimestampOffset() {
58503          _this2.state = 'READY';
58504
58505          if (!_this2.paused()) {
58506            // if not paused, queue a buffer check as soon as possible
58507            _this2.monitorBuffer_();
58508          }
58509        };
58510
58511        this.syncController_.one('timestampoffset', checkTimestampOffset);
58512        this.state = 'WAITING_ON_TIMELINE';
58513        return;
58514      }
58515
58516      this.loadSegment_(segmentInfo);
58517    } // never set a timestamp offset for vtt segments.
58518    ;
58519
58520    _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_() {
58521      return null;
58522    };
58523
58524    _proto.chooseNextRequest_ = function chooseNextRequest_() {
58525      return this.skipEmptySegments_(_SegmentLoader.prototype.chooseNextRequest_.call(this));
58526    }
58527    /**
58528     * Prevents the segment loader from requesting segments we know contain no subtitles
58529     * by walking forward until we find the next segment that we don't know whether it is
58530     * empty or not.
58531     *
58532     * @param {Object} segmentInfo
58533     *        a segment info object that describes the current segment
58534     * @return {Object}
58535     *         a segment info object that describes the current segment
58536     */
58537    ;
58538
58539    _proto.skipEmptySegments_ = function skipEmptySegments_(segmentInfo) {
58540      while (segmentInfo && segmentInfo.segment.empty) {
58541        // stop at the last possible segmentInfo
58542        if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
58543          segmentInfo = null;
58544          break;
58545        }
58546
58547        segmentInfo = this.generateSegmentInfo_({
58548          playlist: segmentInfo.playlist,
58549          mediaIndex: segmentInfo.mediaIndex + 1,
58550          startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
58551          isSyncRequest: segmentInfo.isSyncRequest
58552        });
58553      }
58554
58555      return segmentInfo;
58556    };
58557
58558    _proto.stopForError = function stopForError(error) {
58559      this.error(error);
58560      this.state = 'READY';
58561      this.pause();
58562      this.trigger('error');
58563    }
58564    /**
58565     * append a decrypted segement to the SourceBuffer through a SourceUpdater
58566     *
58567     * @private
58568     */
58569    ;
58570
58571    _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
58572      var _this3 = this;
58573
58574      if (!this.subtitlesTrack_) {
58575        this.state = 'READY';
58576        return;
58577      }
58578
58579      this.saveTransferStats_(simpleSegment.stats); // the request was aborted
58580
58581      if (!this.pendingSegment_) {
58582        this.state = 'READY';
58583        this.mediaRequestsAborted += 1;
58584        return;
58585      }
58586
58587      if (error) {
58588        if (error.code === REQUEST_ERRORS.TIMEOUT) {
58589          this.handleTimeout_();
58590        }
58591
58592        if (error.code === REQUEST_ERRORS.ABORTED) {
58593          this.mediaRequestsAborted += 1;
58594        } else {
58595          this.mediaRequestsErrored += 1;
58596        }
58597
58598        this.stopForError(error);
58599        return;
58600      }
58601
58602      var segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to
58603      // maintain functionality between segment loaders
58604
58605      this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
58606      this.state = 'APPENDING'; // used for tests
58607
58608      this.trigger('appending');
58609      var segment = segmentInfo.segment;
58610
58611      if (segment.map) {
58612        segment.map.bytes = simpleSegment.map.bytes;
58613      }
58614
58615      segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
58616
58617      if (typeof window_1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
58618        var loadHandler;
58619
58620        var errorHandler = function errorHandler() {
58621          _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
58622
58623          _this3.stopForError({
58624            message: 'Error loading vtt.js'
58625          });
58626
58627          return;
58628        };
58629
58630        loadHandler = function loadHandler() {
58631          _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler);
58632
58633          _this3.segmentRequestFinished_(error, simpleSegment, result);
58634        };
58635
58636        this.state = 'WAITING_ON_VTTJS';
58637        this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
58638        this.subtitlesTrack_.tech_.one('vttjserror', errorHandler);
58639        return;
58640      }
58641
58642      segment.requested = true;
58643
58644      try {
58645        this.parseVTTCues_(segmentInfo);
58646      } catch (e) {
58647        this.stopForError({
58648          message: e.message
58649        });
58650        return;
58651      }
58652
58653      this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
58654
58655      if (segmentInfo.cues.length) {
58656        segmentInfo.timingInfo = {
58657          start: segmentInfo.cues[0].startTime,
58658          end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
58659        };
58660      } else {
58661        segmentInfo.timingInfo = {
58662          start: segmentInfo.startOfSegment,
58663          end: segmentInfo.startOfSegment + segmentInfo.duration
58664        };
58665      }
58666
58667      if (segmentInfo.isSyncRequest) {
58668        this.trigger('syncinfoupdate');
58669        this.pendingSegment_ = null;
58670        this.state = 'READY';
58671        return;
58672      }
58673
58674      segmentInfo.byteLength = segmentInfo.bytes.byteLength;
58675      this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
58676      // the subtitle track
58677
58678      segmentInfo.cues.forEach(function (cue) {
58679        _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window_1.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
58680      }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
58681      // cues to have identical time-intervals, but if the text is also identical
58682      // we can safely assume it is a duplicate that can be removed (ex. when a cue
58683      // "overlaps" VTT segments)
58684
58685      removeDuplicateCuesFromTrack(this.subtitlesTrack_);
58686      this.handleAppendsDone_();
58687    };
58688
58689    _proto.handleData_ = function handleData_() {// noop as we shouldn't be getting video/audio data captions
58690      // that we do not support here.
58691    };
58692
58693    _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_() {// noop
58694    }
58695    /**
58696     * Uses the WebVTT parser to parse the segment response
58697     *
58698     * @param {Object} segmentInfo
58699     *        a segment info object that describes the current segment
58700     * @private
58701     */
58702    ;
58703
58704    _proto.parseVTTCues_ = function parseVTTCues_(segmentInfo) {
58705      var decoder;
58706      var decodeBytesToString = false;
58707
58708      if (typeof window_1.TextDecoder === 'function') {
58709        decoder = new window_1.TextDecoder('utf8');
58710      } else {
58711        decoder = window_1.WebVTT.StringDecoder();
58712        decodeBytesToString = true;
58713      }
58714
58715      var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, decoder);
58716      segmentInfo.cues = [];
58717      segmentInfo.timestampmap = {
58718        MPEGTS: 0,
58719        LOCAL: 0
58720      };
58721      parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
58722
58723      parser.ontimestampmap = function (map) {
58724        segmentInfo.timestampmap = map;
58725      };
58726
58727      parser.onparsingerror = function (error) {
58728        videojs.log.warn('Error encountered when parsing cues: ' + error.message);
58729      };
58730
58731      if (segmentInfo.segment.map) {
58732        var mapData = segmentInfo.segment.map.bytes;
58733
58734        if (decodeBytesToString) {
58735          mapData = uint8ToUtf8(mapData);
58736        }
58737
58738        parser.parse(mapData);
58739      }
58740
58741      var segmentData = segmentInfo.bytes;
58742
58743      if (decodeBytesToString) {
58744        segmentData = uint8ToUtf8(segmentData);
58745      }
58746
58747      parser.parse(segmentData);
58748      parser.flush();
58749    }
58750    /**
58751     * Updates the start and end times of any cues parsed by the WebVTT parser using
58752     * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
58753     * from the SyncController
58754     *
58755     * @param {Object} segmentInfo
58756     *        a segment info object that describes the current segment
58757     * @param {Object} mappingObj
58758     *        object containing a mapping from TS to media time
58759     * @param {Object} playlist
58760     *        the playlist object containing the segment
58761     * @private
58762     */
58763    ;
58764
58765    _proto.updateTimeMapping_ = function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
58766      var segment = segmentInfo.segment;
58767
58768      if (!mappingObj) {
58769        // If the sync controller does not have a mapping of TS to Media Time for the
58770        // timeline, then we don't have enough information to update the cue
58771        // start/end times
58772        return;
58773      }
58774
58775      if (!segmentInfo.cues.length) {
58776        // If there are no cues, we also do not have enough information to figure out
58777        // segment timing. Mark that the segment contains no cues so we don't re-request
58778        // an empty segment.
58779        segment.empty = true;
58780        return;
58781      }
58782
58783      var timestampmap = segmentInfo.timestampmap;
58784      var diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping;
58785      segmentInfo.cues.forEach(function (cue) {
58786        // First convert cue time to TS time using the timestamp-map provided within the vtt
58787        cue.startTime += diff;
58788        cue.endTime += diff;
58789      });
58790
58791      if (!playlist.syncInfo) {
58792        var firstStart = segmentInfo.cues[0].startTime;
58793        var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
58794        playlist.syncInfo = {
58795          mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
58796          time: Math.min(firstStart, lastStart - segment.duration)
58797        };
58798      }
58799    };
58800
58801    return VTTSegmentLoader;
58802  }(SegmentLoader);
58803  /**
58804   * @file ad-cue-tags.js
58805   */
58806
58807  /**
58808   * Searches for an ad cue that overlaps with the given mediaTime
58809   *
58810   * @param {Object} track
58811   *        the track to find the cue for
58812   *
58813   * @param {number} mediaTime
58814   *        the time to find the cue at
58815   *
58816   * @return {Object|null}
58817   *         the found cue or null
58818   */
58819
58820
58821  var findAdCue = function findAdCue(track, mediaTime) {
58822    var cues = track.cues;
58823
58824    for (var i = 0; i < cues.length; i++) {
58825      var cue = cues[i];
58826
58827      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
58828        return cue;
58829      }
58830    }
58831
58832    return null;
58833  };
58834
58835  var updateAdCues = function updateAdCues(media, track, offset) {
58836    if (offset === void 0) {
58837      offset = 0;
58838    }
58839
58840    if (!media.segments) {
58841      return;
58842    }
58843
58844    var mediaTime = offset;
58845    var cue;
58846
58847    for (var i = 0; i < media.segments.length; i++) {
58848      var segment = media.segments[i];
58849
58850      if (!cue) {
58851        // Since the cues will span for at least the segment duration, adding a fudge
58852        // factor of half segment duration will prevent duplicate cues from being
58853        // created when timing info is not exact (e.g. cue start time initialized
58854        // at 10.006677, but next call mediaTime is 10.003332 )
58855        cue = findAdCue(track, mediaTime + segment.duration / 2);
58856      }
58857
58858      if (cue) {
58859        if ('cueIn' in segment) {
58860          // Found a CUE-IN so end the cue
58861          cue.endTime = mediaTime;
58862          cue.adEndTime = mediaTime;
58863          mediaTime += segment.duration;
58864          cue = null;
58865          continue;
58866        }
58867
58868        if (mediaTime < cue.endTime) {
58869          // Already processed this mediaTime for this cue
58870          mediaTime += segment.duration;
58871          continue;
58872        } // otherwise extend cue until a CUE-IN is found
58873
58874
58875        cue.endTime += segment.duration;
58876      } else {
58877        if ('cueOut' in segment) {
58878          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
58879          cue.adStartTime = mediaTime; // Assumes tag format to be
58880          // #EXT-X-CUE-OUT:30
58881
58882          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
58883          track.addCue(cue);
58884        }
58885
58886        if ('cueOutCont' in segment) {
58887          // Entered into the middle of an ad cue
58888          // Assumes tag formate to be
58889          // #EXT-X-CUE-OUT-CONT:10/30
58890          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat),
58891              adOffset = _segment$cueOutCont$s[0],
58892              adTotal = _segment$cueOutCont$s[1];
58893
58894          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, '');
58895          cue.adStartTime = mediaTime - adOffset;
58896          cue.adEndTime = cue.adStartTime + adTotal;
58897          track.addCue(cue);
58898        }
58899      }
58900
58901      mediaTime += segment.duration;
58902    }
58903  };
58904
58905  var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
58906  //                the equivalence display-time 0 === segment-index 0
58907  {
58908    name: 'VOD',
58909    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
58910      if (duration !== Infinity) {
58911        var syncPoint = {
58912          time: 0,
58913          segmentIndex: 0,
58914          partIndex: null
58915        };
58916        return syncPoint;
58917      }
58918
58919      return null;
58920    }
58921  }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
58922  {
58923    name: 'ProgramDateTime',
58924    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
58925      if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
58926        return null;
58927      }
58928
58929      var syncPoint = null;
58930      var lastDistance = null;
58931      var partsAndSegments = getPartsAndSegments(playlist);
58932      currentTime = currentTime || 0;
58933
58934      for (var i = 0; i < partsAndSegments.length; i++) {
58935        // start from the end and loop backwards for live
58936        // or start from the front and loop forwards for non-live
58937        var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58938        var partAndSegment = partsAndSegments[index];
58939        var segment = partAndSegment.segment;
58940        var datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
58941
58942        if (!datetimeMapping) {
58943          continue;
58944        }
58945
58946        if (segment.dateTimeObject) {
58947          var segmentTime = segment.dateTimeObject.getTime() / 1000;
58948          var start = segmentTime + datetimeMapping; // take part duration into account.
58949
58950          if (segment.parts && typeof partAndSegment.partIndex === 'number') {
58951            for (var z = 0; z < partAndSegment.partIndex; z++) {
58952              start += segment.parts[z].duration;
58953            }
58954          }
58955
58956          var distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
58957          // currentTime and can stop looking for better candidates
58958
58959          if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
58960            break;
58961          }
58962
58963          lastDistance = distance;
58964          syncPoint = {
58965            time: start,
58966            segmentIndex: partAndSegment.segmentIndex,
58967            partIndex: partAndSegment.partIndex
58968          };
58969        }
58970      }
58971
58972      return syncPoint;
58973    }
58974  }, // Stategy "Segment": We have a known time mapping for a timeline and a
58975  //                    segment in the current timeline with timing data
58976  {
58977    name: 'Segment',
58978    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
58979      var syncPoint = null;
58980      var lastDistance = null;
58981      currentTime = currentTime || 0;
58982      var partsAndSegments = getPartsAndSegments(playlist);
58983
58984      for (var i = 0; i < partsAndSegments.length; i++) {
58985        // start from the end and loop backwards for live
58986        // or start from the front and loop forwards for non-live
58987        var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58988        var partAndSegment = partsAndSegments[index];
58989        var segment = partAndSegment.segment;
58990        var start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
58991
58992        if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
58993          var distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
58994          // currentTime and can stop looking for better candidates
58995
58996          if (lastDistance !== null && lastDistance < distance) {
58997            break;
58998          }
58999
59000          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
59001            lastDistance = distance;
59002            syncPoint = {
59003              time: start,
59004              segmentIndex: partAndSegment.segmentIndex,
59005              partIndex: partAndSegment.partIndex
59006            };
59007          }
59008        }
59009      }
59010
59011      return syncPoint;
59012    }
59013  }, // Stategy "Discontinuity": We have a discontinuity with a known
59014  //                          display-time
59015  {
59016    name: 'Discontinuity',
59017    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
59018      var syncPoint = null;
59019      currentTime = currentTime || 0;
59020
59021      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
59022        var lastDistance = null;
59023
59024        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
59025          var segmentIndex = playlist.discontinuityStarts[i];
59026          var discontinuity = playlist.discontinuitySequence + i + 1;
59027          var discontinuitySync = syncController.discontinuities[discontinuity];
59028
59029          if (discontinuitySync) {
59030            var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
59031            // currentTime and can stop looking for better candidates
59032
59033            if (lastDistance !== null && lastDistance < distance) {
59034              break;
59035            }
59036
59037            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
59038              lastDistance = distance;
59039              syncPoint = {
59040                time: discontinuitySync.time,
59041                segmentIndex: segmentIndex,
59042                partIndex: null
59043              };
59044            }
59045          }
59046        }
59047      }
59048
59049      return syncPoint;
59050    }
59051  }, // Stategy "Playlist": We have a playlist with a known mapping of
59052  //                     segment index to display time
59053  {
59054    name: 'Playlist',
59055    run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
59056      if (playlist.syncInfo) {
59057        var syncPoint = {
59058          time: playlist.syncInfo.time,
59059          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
59060          partIndex: null
59061        };
59062        return syncPoint;
59063      }
59064
59065      return null;
59066    }
59067  }];
59068
59069  var SyncController = /*#__PURE__*/function (_videojs$EventTarget) {
59070    inheritsLoose(SyncController, _videojs$EventTarget);
59071
59072    function SyncController(options) {
59073      var _this;
59074
59075      _this = _videojs$EventTarget.call(this) || this; // ...for synching across variants
59076
59077      _this.timelines = [];
59078      _this.discontinuities = [];
59079      _this.timelineToDatetimeMappings = {};
59080      _this.logger_ = logger('SyncController');
59081      return _this;
59082    }
59083    /**
59084     * Find a sync-point for the playlist specified
59085     *
59086     * A sync-point is defined as a known mapping from display-time to
59087     * a segment-index in the current playlist.
59088     *
59089     * @param {Playlist} playlist
59090     *        The playlist that needs a sync-point
59091     * @param {number} duration
59092     *        Duration of the MediaSource (Infinite if playing a live source)
59093     * @param {number} currentTimeline
59094     *        The last timeline from which a segment was loaded
59095     * @return {Object}
59096     *          A sync-point object
59097     */
59098
59099
59100    var _proto = SyncController.prototype;
59101
59102    _proto.getSyncPoint = function getSyncPoint(playlist, duration, currentTimeline, currentTime) {
59103      var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime);
59104
59105      if (!syncPoints.length) {
59106        // Signal that we need to attempt to get a sync-point manually
59107        // by fetching a segment in the playlist and constructing
59108        // a sync-point from that information
59109        return null;
59110      } // Now find the sync-point that is closest to the currentTime because
59111      // that should result in the most accurate guess about which segment
59112      // to fetch
59113
59114
59115      return this.selectSyncPoint_(syncPoints, {
59116        key: 'time',
59117        value: currentTime
59118      });
59119    }
59120    /**
59121     * Calculate the amount of time that has expired off the playlist during playback
59122     *
59123     * @param {Playlist} playlist
59124     *        Playlist object to calculate expired from
59125     * @param {number} duration
59126     *        Duration of the MediaSource (Infinity if playling a live source)
59127     * @return {number|null}
59128     *          The amount of time that has expired off the playlist during playback. Null
59129     *          if no sync-points for the playlist can be found.
59130     */
59131    ;
59132
59133    _proto.getExpiredTime = function getExpiredTime(playlist, duration) {
59134      if (!playlist || !playlist.segments) {
59135        return null;
59136      }
59137
59138      var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
59139
59140      if (!syncPoints.length) {
59141        return null;
59142      }
59143
59144      var syncPoint = this.selectSyncPoint_(syncPoints, {
59145        key: 'segmentIndex',
59146        value: 0
59147      }); // If the sync-point is beyond the start of the playlist, we want to subtract the
59148      // duration from index 0 to syncPoint.segmentIndex instead of adding.
59149
59150      if (syncPoint.segmentIndex > 0) {
59151        syncPoint.time *= -1;
59152      }
59153
59154      return Math.abs(syncPoint.time + sumDurations({
59155        defaultDuration: playlist.targetDuration,
59156        durationList: playlist.segments,
59157        startIndex: syncPoint.segmentIndex,
59158        endIndex: 0
59159      }));
59160    }
59161    /**
59162     * Runs each sync-point strategy and returns a list of sync-points returned by the
59163     * strategies
59164     *
59165     * @private
59166     * @param {Playlist} playlist
59167     *        The playlist that needs a sync-point
59168     * @param {number} duration
59169     *        Duration of the MediaSource (Infinity if playing a live source)
59170     * @param {number} currentTimeline
59171     *        The last timeline from which a segment was loaded
59172     * @return {Array}
59173     *          A list of sync-point objects
59174     */
59175    ;
59176
59177    _proto.runStrategies_ = function runStrategies_(playlist, duration, currentTimeline, currentTime) {
59178      var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
59179
59180      for (var i = 0; i < syncPointStrategies.length; i++) {
59181        var strategy = syncPointStrategies[i];
59182        var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime);
59183
59184        if (syncPoint) {
59185          syncPoint.strategy = strategy.name;
59186          syncPoints.push({
59187            strategy: strategy.name,
59188            syncPoint: syncPoint
59189          });
59190        }
59191      }
59192
59193      return syncPoints;
59194    }
59195    /**
59196     * Selects the sync-point nearest the specified target
59197     *
59198     * @private
59199     * @param {Array} syncPoints
59200     *        List of sync-points to select from
59201     * @param {Object} target
59202     *        Object specifying the property and value we are targeting
59203     * @param {string} target.key
59204     *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
59205     * @param {number} target.value
59206     *        The value to target for the specified key.
59207     * @return {Object}
59208     *          The sync-point nearest the target
59209     */
59210    ;
59211
59212    _proto.selectSyncPoint_ = function selectSyncPoint_(syncPoints, target) {
59213      var bestSyncPoint = syncPoints[0].syncPoint;
59214      var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
59215      var bestStrategy = syncPoints[0].strategy;
59216
59217      for (var i = 1; i < syncPoints.length; i++) {
59218        var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
59219
59220        if (newDistance < bestDistance) {
59221          bestDistance = newDistance;
59222          bestSyncPoint = syncPoints[i].syncPoint;
59223          bestStrategy = syncPoints[i].strategy;
59224        }
59225      }
59226
59227      this.logger_("syncPoint for [" + target.key + ": " + target.value + "] chosen with strategy" + (" [" + bestStrategy + "]: [time:" + bestSyncPoint.time + ",") + (" segmentIndex:" + bestSyncPoint.segmentIndex) + (typeof bestSyncPoint.partIndex === 'number' ? ",partIndex:" + bestSyncPoint.partIndex : '') + ']');
59228      return bestSyncPoint;
59229    }
59230    /**
59231     * Save any meta-data present on the segments when segments leave
59232     * the live window to the playlist to allow for synchronization at the
59233     * playlist level later.
59234     *
59235     * @param {Playlist} oldPlaylist - The previous active playlist
59236     * @param {Playlist} newPlaylist - The updated and most current playlist
59237     */
59238    ;
59239
59240    _proto.saveExpiredSegmentInfo = function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
59241      var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
59242      // save that information as a possible sync-point reference in future
59243
59244      for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
59245        var lastRemovedSegment = oldPlaylist.segments[i];
59246
59247        if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
59248          newPlaylist.syncInfo = {
59249            mediaSequence: oldPlaylist.mediaSequence + i,
59250            time: lastRemovedSegment.start
59251          };
59252          this.logger_("playlist refresh sync: [time:" + newPlaylist.syncInfo.time + "," + (" mediaSequence: " + newPlaylist.syncInfo.mediaSequence + "]"));
59253          this.trigger('syncinfoupdate');
59254          break;
59255        }
59256      }
59257    }
59258    /**
59259     * Save the mapping from playlist's ProgramDateTime to display. This should only happen
59260     * before segments start to load.
59261     *
59262     * @param {Playlist} playlist - The currently active playlist
59263     */
59264    ;
59265
59266    _proto.setDateTimeMappingForStart = function setDateTimeMappingForStart(playlist) {
59267      // It's possible for the playlist to be updated before playback starts, meaning time
59268      // zero is not yet set. If, during these playlist refreshes, a discontinuity is
59269      // crossed, then the old time zero mapping (for the prior timeline) would be retained
59270      // unless the mappings are cleared.
59271      this.timelineToDatetimeMappings = {};
59272
59273      if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
59274        var firstSegment = playlist.segments[0];
59275        var playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
59276        this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
59277      }
59278    }
59279    /**
59280     * Calculates and saves timeline mappings, playlist sync info, and segment timing values
59281     * based on the latest timing information.
59282     *
59283     * @param {Object} options
59284     *        Options object
59285     * @param {SegmentInfo} options.segmentInfo
59286     *        The current active request information
59287     * @param {boolean} options.shouldSaveTimelineMapping
59288     *        If there's a timeline change, determines if the timeline mapping should be
59289     *        saved for timeline mapping and program date time mappings.
59290     */
59291    ;
59292
59293    _proto.saveSegmentTimingInfo = function saveSegmentTimingInfo(_ref) {
59294      var segmentInfo = _ref.segmentInfo,
59295          shouldSaveTimelineMapping = _ref.shouldSaveTimelineMapping;
59296      var didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
59297      var segment = segmentInfo.segment;
59298
59299      if (didCalculateSegmentTimeMapping) {
59300        this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
59301        // now with segment timing information
59302
59303        if (!segmentInfo.playlist.syncInfo) {
59304          segmentInfo.playlist.syncInfo = {
59305            mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
59306            time: segment.start
59307          };
59308        }
59309      }
59310
59311      var dateTime = segment.dateTimeObject;
59312
59313      if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
59314        this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
59315      }
59316    };
59317
59318    _proto.timestampOffsetForTimeline = function timestampOffsetForTimeline(timeline) {
59319      if (typeof this.timelines[timeline] === 'undefined') {
59320        return null;
59321      }
59322
59323      return this.timelines[timeline].time;
59324    };
59325
59326    _proto.mappingForTimeline = function mappingForTimeline(timeline) {
59327      if (typeof this.timelines[timeline] === 'undefined') {
59328        return null;
59329      }
59330
59331      return this.timelines[timeline].mapping;
59332    }
59333    /**
59334     * Use the "media time" for a segment to generate a mapping to "display time" and
59335     * save that display time to the segment.
59336     *
59337     * @private
59338     * @param {SegmentInfo} segmentInfo
59339     *        The current active request information
59340     * @param {Object} timingInfo
59341     *        The start and end time of the current segment in "media time"
59342     * @param {boolean} shouldSaveTimelineMapping
59343     *        If there's a timeline change, determines if the timeline mapping should be
59344     *        saved in timelines.
59345     * @return {boolean}
59346     *          Returns false if segment time mapping could not be calculated
59347     */
59348    ;
59349
59350    _proto.calculateSegmentTimeMapping_ = function calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
59351      // TODO: remove side effects
59352      var segment = segmentInfo.segment;
59353      var part = segmentInfo.part;
59354      var mappingObj = this.timelines[segmentInfo.timeline];
59355      var start;
59356      var end;
59357
59358      if (typeof segmentInfo.timestampOffset === 'number') {
59359        mappingObj = {
59360          time: segmentInfo.startOfSegment,
59361          mapping: segmentInfo.startOfSegment - timingInfo.start
59362        };
59363
59364        if (shouldSaveTimelineMapping) {
59365          this.timelines[segmentInfo.timeline] = mappingObj;
59366          this.trigger('timestampoffset');
59367          this.logger_("time mapping for timeline " + segmentInfo.timeline + ": " + ("[time: " + mappingObj.time + "] [mapping: " + mappingObj.mapping + "]"));
59368        }
59369
59370        start = segmentInfo.startOfSegment;
59371        end = timingInfo.end + mappingObj.mapping;
59372      } else if (mappingObj) {
59373        start = timingInfo.start + mappingObj.mapping;
59374        end = timingInfo.end + mappingObj.mapping;
59375      } else {
59376        return false;
59377      }
59378
59379      if (part) {
59380        part.start = start;
59381        part.end = end;
59382      } // If we don't have a segment start yet or the start value we got
59383      // is less than our current segment.start value, save a new start value.
59384      // We have to do this because parts will have segment timing info saved
59385      // multiple times and we want segment start to be the earliest part start
59386      // value for that segment.
59387
59388
59389      if (!segment.start || start < segment.start) {
59390        segment.start = start;
59391      }
59392
59393      segment.end = end;
59394      return true;
59395    }
59396    /**
59397     * Each time we have discontinuity in the playlist, attempt to calculate the location
59398     * in display of the start of the discontinuity and save that. We also save an accuracy
59399     * value so that we save values with the most accuracy (closest to 0.)
59400     *
59401     * @private
59402     * @param {SegmentInfo} segmentInfo - The current active request information
59403     */
59404    ;
59405
59406    _proto.saveDiscontinuitySyncInfo_ = function saveDiscontinuitySyncInfo_(segmentInfo) {
59407      var playlist = segmentInfo.playlist;
59408      var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
59409      // the start of the range and it's accuracy is 0 (greater accuracy values
59410      // mean more approximation)
59411
59412      if (segment.discontinuity) {
59413        this.discontinuities[segment.timeline] = {
59414          time: segment.start,
59415          accuracy: 0
59416        };
59417      } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
59418        // Search for future discontinuities that we can provide better timing
59419        // information for and save that information for sync purposes
59420        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
59421          var segmentIndex = playlist.discontinuityStarts[i];
59422          var discontinuity = playlist.discontinuitySequence + i + 1;
59423          var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
59424          var accuracy = Math.abs(mediaIndexDiff);
59425
59426          if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
59427            var time = void 0;
59428
59429            if (mediaIndexDiff < 0) {
59430              time = segment.start - sumDurations({
59431                defaultDuration: playlist.targetDuration,
59432                durationList: playlist.segments,
59433                startIndex: segmentInfo.mediaIndex,
59434                endIndex: segmentIndex
59435              });
59436            } else {
59437              time = segment.end + sumDurations({
59438                defaultDuration: playlist.targetDuration,
59439                durationList: playlist.segments,
59440                startIndex: segmentInfo.mediaIndex + 1,
59441                endIndex: segmentIndex
59442              });
59443            }
59444
59445            this.discontinuities[discontinuity] = {
59446              time: time,
59447              accuracy: accuracy
59448            };
59449          }
59450        }
59451      }
59452    };
59453
59454    _proto.dispose = function dispose() {
59455      this.trigger('dispose');
59456      this.off();
59457    };
59458
59459    return SyncController;
59460  }(videojs.EventTarget);
59461  /**
59462   * The TimelineChangeController acts as a source for segment loaders to listen for and
59463   * keep track of latest and pending timeline changes. This is useful to ensure proper
59464   * sync, as each loader may need to make a consideration for what timeline the other
59465   * loader is on before making changes which could impact the other loader's media.
59466   *
59467   * @class TimelineChangeController
59468   * @extends videojs.EventTarget
59469   */
59470
59471
59472  var TimelineChangeController = /*#__PURE__*/function (_videojs$EventTarget) {
59473    inheritsLoose(TimelineChangeController, _videojs$EventTarget);
59474
59475    function TimelineChangeController() {
59476      var _this;
59477
59478      _this = _videojs$EventTarget.call(this) || this;
59479      _this.pendingTimelineChanges_ = {};
59480      _this.lastTimelineChanges_ = {};
59481      return _this;
59482    }
59483
59484    var _proto = TimelineChangeController.prototype;
59485
59486    _proto.clearPendingTimelineChange = function clearPendingTimelineChange(type) {
59487      this.pendingTimelineChanges_[type] = null;
59488      this.trigger('pendingtimelinechange');
59489    };
59490
59491    _proto.pendingTimelineChange = function pendingTimelineChange(_ref) {
59492      var type = _ref.type,
59493          from = _ref.from,
59494          to = _ref.to;
59495
59496      if (typeof from === 'number' && typeof to === 'number') {
59497        this.pendingTimelineChanges_[type] = {
59498          type: type,
59499          from: from,
59500          to: to
59501        };
59502        this.trigger('pendingtimelinechange');
59503      }
59504
59505      return this.pendingTimelineChanges_[type];
59506    };
59507
59508    _proto.lastTimelineChange = function lastTimelineChange(_ref2) {
59509      var type = _ref2.type,
59510          from = _ref2.from,
59511          to = _ref2.to;
59512
59513      if (typeof from === 'number' && typeof to === 'number') {
59514        this.lastTimelineChanges_[type] = {
59515          type: type,
59516          from: from,
59517          to: to
59518        };
59519        delete this.pendingTimelineChanges_[type];
59520        this.trigger('timelinechange');
59521      }
59522
59523      return this.lastTimelineChanges_[type];
59524    };
59525
59526    _proto.dispose = function dispose() {
59527      this.trigger('dispose');
59528      this.pendingTimelineChanges_ = {};
59529      this.lastTimelineChanges_ = {};
59530      this.off();
59531    };
59532
59533    return TimelineChangeController;
59534  }(videojs.EventTarget);
59535  /* rollup-plugin-worker-factory start for worker!/Users/bcasey/Projects/videojs-http-streaming/src/decrypter-worker.js */
59536
59537
59538  var workerCode = transform(getWorkerString(function () {
59539    function createCommonjsModule(fn, basedir, module) {
59540      return module = {
59541        path: basedir,
59542        exports: {},
59543        require: function require(path, base) {
59544          return commonjsRequire(path, base === undefined || base === null ? module.path : base);
59545        }
59546      }, fn(module, module.exports), module.exports;
59547    }
59548
59549    function commonjsRequire() {
59550      throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
59551    }
59552
59553    var createClass = createCommonjsModule(function (module) {
59554      function _defineProperties(target, props) {
59555        for (var i = 0; i < props.length; i++) {
59556          var descriptor = props[i];
59557          descriptor.enumerable = descriptor.enumerable || false;
59558          descriptor.configurable = true;
59559          if ("value" in descriptor) descriptor.writable = true;
59560          Object.defineProperty(target, descriptor.key, descriptor);
59561        }
59562      }
59563
59564      function _createClass(Constructor, protoProps, staticProps) {
59565        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
59566        if (staticProps) _defineProperties(Constructor, staticProps);
59567        return Constructor;
59568      }
59569
59570      module.exports = _createClass;
59571      module.exports["default"] = module.exports, module.exports.__esModule = true;
59572    });
59573    var setPrototypeOf = createCommonjsModule(function (module) {
59574      function _setPrototypeOf(o, p) {
59575        module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
59576          o.__proto__ = p;
59577          return o;
59578        };
59579
59580        module.exports["default"] = module.exports, module.exports.__esModule = true;
59581        return _setPrototypeOf(o, p);
59582      }
59583
59584      module.exports = _setPrototypeOf;
59585      module.exports["default"] = module.exports, module.exports.__esModule = true;
59586    });
59587    var inheritsLoose = createCommonjsModule(function (module) {
59588      function _inheritsLoose(subClass, superClass) {
59589        subClass.prototype = Object.create(superClass.prototype);
59590        subClass.prototype.constructor = subClass;
59591        setPrototypeOf(subClass, superClass);
59592      }
59593
59594      module.exports = _inheritsLoose;
59595      module.exports["default"] = module.exports, module.exports.__esModule = true;
59596    });
59597    /**
59598     * @file stream.js
59599     */
59600
59601    /**
59602     * A lightweight readable stream implemention that handles event dispatching.
59603     *
59604     * @class Stream
59605     */
59606
59607    var Stream = /*#__PURE__*/function () {
59608      function Stream() {
59609        this.listeners = {};
59610      }
59611      /**
59612       * Add a listener for a specified event type.
59613       *
59614       * @param {string} type the event name
59615       * @param {Function} listener the callback to be invoked when an event of
59616       * the specified type occurs
59617       */
59618
59619
59620      var _proto = Stream.prototype;
59621
59622      _proto.on = function on(type, listener) {
59623        if (!this.listeners[type]) {
59624          this.listeners[type] = [];
59625        }
59626
59627        this.listeners[type].push(listener);
59628      }
59629      /**
59630       * Remove a listener for a specified event type.
59631       *
59632       * @param {string} type the event name
59633       * @param {Function} listener  a function previously registered for this
59634       * type of event through `on`
59635       * @return {boolean} if we could turn it off or not
59636       */
59637      ;
59638
59639      _proto.off = function off(type, listener) {
59640        if (!this.listeners[type]) {
59641          return false;
59642        }
59643
59644        var index = this.listeners[type].indexOf(listener); // TODO: which is better?
59645        // In Video.js we slice listener functions
59646        // on trigger so that it does not mess up the order
59647        // while we loop through.
59648        //
59649        // Here we slice on off so that the loop in trigger
59650        // can continue using it's old reference to loop without
59651        // messing up the order.
59652
59653        this.listeners[type] = this.listeners[type].slice(0);
59654        this.listeners[type].splice(index, 1);
59655        return index > -1;
59656      }
59657      /**
59658       * Trigger an event of the specified type on this stream. Any additional
59659       * arguments to this function are passed as parameters to event listeners.
59660       *
59661       * @param {string} type the event name
59662       */
59663      ;
59664
59665      _proto.trigger = function trigger(type) {
59666        var callbacks = this.listeners[type];
59667
59668        if (!callbacks) {
59669          return;
59670        } // Slicing the arguments on every invocation of this method
59671        // can add a significant amount of overhead. Avoid the
59672        // intermediate object creation for the common case of a
59673        // single callback argument
59674
59675
59676        if (arguments.length === 2) {
59677          var length = callbacks.length;
59678
59679          for (var i = 0; i < length; ++i) {
59680            callbacks[i].call(this, arguments[1]);
59681          }
59682        } else {
59683          var args = Array.prototype.slice.call(arguments, 1);
59684          var _length = callbacks.length;
59685
59686          for (var _i = 0; _i < _length; ++_i) {
59687            callbacks[_i].apply(this, args);
59688          }
59689        }
59690      }
59691      /**
59692       * Destroys the stream and cleans up.
59693       */
59694      ;
59695
59696      _proto.dispose = function dispose() {
59697        this.listeners = {};
59698      }
59699      /**
59700       * Forwards all `data` events on this stream to the destination stream. The
59701       * destination stream should provide a method `push` to receive the data
59702       * events as they arrive.
59703       *
59704       * @param {Stream} destination the stream that will receive all `data` events
59705       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
59706       */
59707      ;
59708
59709      _proto.pipe = function pipe(destination) {
59710        this.on('data', function (data) {
59711          destination.push(data);
59712        });
59713      };
59714
59715      return Stream;
59716    }();
59717    /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
59718
59719    /**
59720     * Returns the subarray of a Uint8Array without PKCS#7 padding.
59721     *
59722     * @param padded {Uint8Array} unencrypted bytes that have been padded
59723     * @return {Uint8Array} the unpadded bytes
59724     * @see http://tools.ietf.org/html/rfc5652
59725     */
59726
59727
59728    function unpad(padded) {
59729      return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
59730    }
59731    /*! @name aes-decrypter @version 3.1.2 @license Apache-2.0 */
59732
59733    /**
59734     * @file aes.js
59735     *
59736     * This file contains an adaptation of the AES decryption algorithm
59737     * from the Standford Javascript Cryptography Library. That work is
59738     * covered by the following copyright and permissions notice:
59739     *
59740     * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
59741     * All rights reserved.
59742     *
59743     * Redistribution and use in source and binary forms, with or without
59744     * modification, are permitted provided that the following conditions are
59745     * met:
59746     *
59747     * 1. Redistributions of source code must retain the above copyright
59748     *    notice, this list of conditions and the following disclaimer.
59749     *
59750     * 2. Redistributions in binary form must reproduce the above
59751     *    copyright notice, this list of conditions and the following
59752     *    disclaimer in the documentation and/or other materials provided
59753     *    with the distribution.
59754     *
59755     * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
59756     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
59757     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
59758     * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
59759     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
59760     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
59761     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
59762     * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
59763     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
59764     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
59765     * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59766     *
59767     * The views and conclusions contained in the software and documentation
59768     * are those of the authors and should not be interpreted as representing
59769     * official policies, either expressed or implied, of the authors.
59770     */
59771
59772    /**
59773     * Expand the S-box tables.
59774     *
59775     * @private
59776     */
59777
59778
59779    var precompute = function precompute() {
59780      var tables = [[[], [], [], [], []], [[], [], [], [], []]];
59781      var encTable = tables[0];
59782      var decTable = tables[1];
59783      var sbox = encTable[4];
59784      var sboxInv = decTable[4];
59785      var i;
59786      var x;
59787      var xInv;
59788      var d = [];
59789      var th = [];
59790      var x2;
59791      var x4;
59792      var x8;
59793      var s;
59794      var tEnc;
59795      var tDec; // Compute double and third tables
59796
59797      for (i = 0; i < 256; i++) {
59798        th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
59799      }
59800
59801      for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
59802        // Compute sbox
59803        s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
59804        s = s >> 8 ^ s & 255 ^ 99;
59805        sbox[x] = s;
59806        sboxInv[s] = x; // Compute MixColumns
59807
59808        x8 = d[x4 = d[x2 = d[x]]];
59809        tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
59810        tEnc = d[s] * 0x101 ^ s * 0x1010100;
59811
59812        for (i = 0; i < 4; i++) {
59813          encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
59814          decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
59815        }
59816      } // Compactify. Considerable speedup on Firefox.
59817
59818
59819      for (i = 0; i < 5; i++) {
59820        encTable[i] = encTable[i].slice(0);
59821        decTable[i] = decTable[i].slice(0);
59822      }
59823
59824      return tables;
59825    };
59826
59827    var aesTables = null;
59828    /**
59829     * Schedule out an AES key for both encryption and decryption. This
59830     * is a low-level class. Use a cipher mode to do bulk encryption.
59831     *
59832     * @class AES
59833     * @param key {Array} The key as an array of 4, 6 or 8 words.
59834     */
59835
59836    var AES = /*#__PURE__*/function () {
59837      function AES(key) {
59838        /**
59839        * The expanded S-box and inverse S-box tables. These will be computed
59840        * on the client so that we don't have to send them down the wire.
59841        *
59842        * There are two tables, _tables[0] is for encryption and
59843        * _tables[1] is for decryption.
59844        *
59845        * The first 4 sub-tables are the expanded S-box with MixColumns. The
59846        * last (_tables[01][4]) is the S-box itself.
59847        *
59848        * @private
59849        */
59850        // if we have yet to precompute the S-box tables
59851        // do so now
59852        if (!aesTables) {
59853          aesTables = precompute();
59854        } // then make a copy of that object for use
59855
59856
59857        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()]];
59858        var i;
59859        var j;
59860        var tmp;
59861        var sbox = this._tables[0][4];
59862        var decTable = this._tables[1];
59863        var keyLen = key.length;
59864        var rcon = 1;
59865
59866        if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
59867          throw new Error('Invalid aes key size');
59868        }
59869
59870        var encKey = key.slice(0);
59871        var decKey = [];
59872        this._key = [encKey, decKey]; // schedule encryption keys
59873
59874        for (i = keyLen; i < 4 * keyLen + 28; i++) {
59875          tmp = encKey[i - 1]; // apply sbox
59876
59877          if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
59878            tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
59879
59880            if (i % keyLen === 0) {
59881              tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
59882              rcon = rcon << 1 ^ (rcon >> 7) * 283;
59883            }
59884          }
59885
59886          encKey[i] = encKey[i - keyLen] ^ tmp;
59887        } // schedule decryption keys
59888
59889
59890        for (j = 0; i; j++, i--) {
59891          tmp = encKey[j & 3 ? i : i - 4];
59892
59893          if (i <= 4 || j < 4) {
59894            decKey[j] = tmp;
59895          } else {
59896            decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
59897          }
59898        }
59899      }
59900      /**
59901       * Decrypt 16 bytes, specified as four 32-bit words.
59902       *
59903       * @param {number} encrypted0 the first word to decrypt
59904       * @param {number} encrypted1 the second word to decrypt
59905       * @param {number} encrypted2 the third word to decrypt
59906       * @param {number} encrypted3 the fourth word to decrypt
59907       * @param {Int32Array} out the array to write the decrypted words
59908       * into
59909       * @param {number} offset the offset into the output array to start
59910       * writing results
59911       * @return {Array} The plaintext.
59912       */
59913
59914
59915      var _proto = AES.prototype;
59916
59917      _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
59918        var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
59919
59920        var a = encrypted0 ^ key[0];
59921        var b = encrypted3 ^ key[1];
59922        var c = encrypted2 ^ key[2];
59923        var d = encrypted1 ^ key[3];
59924        var a2;
59925        var b2;
59926        var c2; // key.length === 2 ?
59927
59928        var nInnerRounds = key.length / 4 - 2;
59929        var i;
59930        var kIndex = 4;
59931        var table = this._tables[1]; // load up the tables
59932
59933        var table0 = table[0];
59934        var table1 = table[1];
59935        var table2 = table[2];
59936        var table3 = table[3];
59937        var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
59938
59939        for (i = 0; i < nInnerRounds; i++) {
59940          a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
59941          b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
59942          c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
59943          d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
59944          kIndex += 4;
59945          a = a2;
59946          b = b2;
59947          c = c2;
59948        } // Last round.
59949
59950
59951        for (i = 0; i < 4; i++) {
59952          out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
59953          a2 = a;
59954          a = b;
59955          b = c;
59956          c = d;
59957          d = a2;
59958        }
59959      };
59960
59961      return AES;
59962    }();
59963    /**
59964     * A wrapper around the Stream class to use setTimeout
59965     * and run stream "jobs" Asynchronously
59966     *
59967     * @class AsyncStream
59968     * @extends Stream
59969     */
59970
59971
59972    var AsyncStream = /*#__PURE__*/function (_Stream) {
59973      inheritsLoose(AsyncStream, _Stream);
59974
59975      function AsyncStream() {
59976        var _this;
59977
59978        _this = _Stream.call(this, Stream) || this;
59979        _this.jobs = [];
59980        _this.delay = 1;
59981        _this.timeout_ = null;
59982        return _this;
59983      }
59984      /**
59985       * process an async job
59986       *
59987       * @private
59988       */
59989
59990
59991      var _proto = AsyncStream.prototype;
59992
59993      _proto.processJob_ = function processJob_() {
59994        this.jobs.shift()();
59995
59996        if (this.jobs.length) {
59997          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
59998        } else {
59999          this.timeout_ = null;
60000        }
60001      }
60002      /**
60003       * push a job into the stream
60004       *
60005       * @param {Function} job the job to push into the stream
60006       */
60007      ;
60008
60009      _proto.push = function push(job) {
60010        this.jobs.push(job);
60011
60012        if (!this.timeout_) {
60013          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
60014        }
60015      };
60016
60017      return AsyncStream;
60018    }(Stream);
60019    /**
60020     * Convert network-order (big-endian) bytes into their little-endian
60021     * representation.
60022     */
60023
60024
60025    var ntoh = function ntoh(word) {
60026      return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
60027    };
60028    /**
60029     * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
60030     *
60031     * @param {Uint8Array} encrypted the encrypted bytes
60032     * @param {Uint32Array} key the bytes of the decryption key
60033     * @param {Uint32Array} initVector the initialization vector (IV) to
vendor: 6,719 bytes, lines 60034-60213
60034     * use for the first round of CBC.
60035     * @return {Uint8Array} the decrypted bytes
60036     *
60037     * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
60038     * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
60039     * @see https://tools.ietf.org/html/rfc2315
60040     */
60041
60042
60043    var decrypt = function decrypt(encrypted, key, initVector) {
60044      // word-level access to the encrypted bytes
60045      var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
60046      var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
60047
60048      var decrypted = new Uint8Array(encrypted.byteLength);
60049      var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
60050      // decrypted data
60051
60052      var init0;
60053      var init1;
60054      var init2;
60055      var init3;
60056      var encrypted0;
60057      var encrypted1;
60058      var encrypted2;
60059      var encrypted3; // iteration variable
60060
60061      var wordIx; // pull out the words of the IV to ensure we don't modify the
60062      // passed-in reference and easier access
60063
60064      init0 = initVector[0];
60065      init1 = initVector[1];
60066      init2 = initVector[2];
60067      init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
60068      // to each decrypted block
60069
60070      for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
60071        // convert big-endian (network order) words into little-endian
60072        // (javascript order)
60073        encrypted0 = ntoh(encrypted32[wordIx]);
60074        encrypted1 = ntoh(encrypted32[wordIx + 1]);
60075        encrypted2 = ntoh(encrypted32[wordIx + 2]);
60076        encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
60077
60078        decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
60079        // plaintext
60080
60081        decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
60082        decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
60083        decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
60084        decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
60085
60086        init0 = encrypted0;
60087        init1 = encrypted1;
60088        init2 = encrypted2;
60089        init3 = encrypted3;
60090      }
60091
60092      return decrypted;
60093    };
60094    /**
60095     * The `Decrypter` class that manages decryption of AES
60096     * data through `AsyncStream` objects and the `decrypt`
60097     * function
60098     *
60099     * @param {Uint8Array} encrypted the encrypted bytes
60100     * @param {Uint32Array} key the bytes of the decryption key
60101     * @param {Uint32Array} initVector the initialization vector (IV) to
60102     * @param {Function} done the function to run when done
60103     * @class Decrypter
60104     */
60105
60106
60107    var Decrypter = /*#__PURE__*/function () {
60108      function Decrypter(encrypted, key, initVector, done) {
60109        var step = Decrypter.STEP;
60110        var encrypted32 = new Int32Array(encrypted.buffer);
60111        var decrypted = new Uint8Array(encrypted.byteLength);
60112        var i = 0;
60113        this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
60114
60115        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
60116
60117        for (i = step; i < encrypted32.length; i += step) {
60118          initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
60119          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
60120        } // invoke the done() callback when everything is finished
60121
60122
60123        this.asyncStream_.push(function () {
60124          // remove pkcs#7 padding from the decrypted bytes
60125          done(null, unpad(decrypted));
60126        });
60127      }
60128      /**
60129       * a getter for step the maximum number of bytes to process at one time
60130       *
60131       * @return {number} the value of step 32000
60132       */
60133
60134
60135      var _proto = Decrypter.prototype;
60136      /**
60137       * @private
60138       */
60139
60140      _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
60141        return function () {
60142          var bytes = decrypt(encrypted, key, initVector);
60143          decrypted.set(bytes, encrypted.byteOffset);
60144        };
60145      };
60146
60147      createClass(Decrypter, null, [{
60148        key: "STEP",
60149        get: function get() {
60150          // 4 * 8000;
60151          return 32000;
60152        }
60153      }]);
60154      return Decrypter;
60155    }();
60156    /**
60157     * @file bin-utils.js
60158     */
60159
60160    /**
60161     * Creates an object for sending to a web worker modifying properties that are TypedArrays
60162     * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
60163     *
60164     * @param {Object} message
60165     *        Object of properties and values to send to the web worker
60166     * @return {Object}
60167     *         Modified message with TypedArray values expanded
60168     * @function createTransferableMessage
60169     */
60170
60171
60172    var createTransferableMessage = function createTransferableMessage(message) {
60173      var transferable = {};
60174      Object.keys(message).forEach(function (key) {
60175        var value = message[key];
60176
60177        if (ArrayBuffer.isView(value)) {
60178          transferable[key] = {
60179            bytes: value.buffer,
60180            byteOffset: value.byteOffset,
60181            byteLength: value.byteLength
60182          };
60183        } else {
60184          transferable[key] = value;
60185        }
60186      });
60187      return transferable;
60188    };
60189    /* global self */
60190
60191    /**
60192     * Our web worker interface so that things can talk to aes-decrypter
60193     * that will be running in a web worker. the scope is passed to this by
60194     * webworkify.
60195     */
60196
60197
60198    self.onmessage = function (event) {
60199      var data = event.data;
60200      var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
60201      var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
60202      var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
60203      /* eslint-disable no-new, handle-callback-err */
60204
60205      new Decrypter(encrypted, key, iv, function (err, bytes) {
60206        self.postMessage(createTransferableMessage({
60207          source: data.source,
60208          decrypted: bytes
60209        }), [bytes.buffer]);
60210      });
60211      /* eslint-enable */
60212    };
60213  }));
60214  var Decrypter = factory(workerCode);
60215  /* rollup-plugin-worker-factory end for worker!/Users/bcasey/Projects/videojs-http-streaming/src/decrypter-worker.js */
60216
60217  /**
60218   * Convert the properties of an HLS track into an audioTrackKind.
60219   *
60220   * @private
60221   */
60222
60223  var audioTrackKind_ = function audioTrackKind_(properties) {
60224    var kind = properties["default"] ? 'main' : 'alternative';
60225
60226    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
60227      kind = 'main-desc';
60228    }
60229
60230    return kind;
60231  };
60232  /**
60233   * Pause provided segment loader and playlist loader if active
60234   *
60235   * @param {SegmentLoader} segmentLoader
60236   *        SegmentLoader to pause
60237   * @param {Object} mediaType
60238   *        Active media type
60239   * @function stopLoaders
60240   */
60241
60242
60243  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
60244    segmentLoader.abort();
60245    segmentLoader.pause();
60246
60247    if (mediaType && mediaType.activePlaylistLoader) {
60248      mediaType.activePlaylistLoader.pause();
60249      mediaType.activePlaylistLoader = null;
60250    }
60251  };
60252  /**
60253   * Start loading provided segment loader and playlist loader
60254   *
60255   * @param {PlaylistLoader} playlistLoader
60256   *        PlaylistLoader to start loading
60257   * @param {Object} mediaType
60258   *        Active media type
60259   * @function startLoaders
60260   */
60261
60262
60263  var startLoaders = function startLoaders(playlistLoader, mediaType) {
60264    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
60265    // playlist loader
60266    mediaType.activePlaylistLoader = playlistLoader;
60267    playlistLoader.load();
60268  };
60269  /**
60270   * Returns a function to be called when the media group changes. It performs a
60271   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
60272   * change of group is merely a rendition switch of the same content at another encoding,
60273   * rather than a change of content, such as switching audio from English to Spanish.
60274   *
60275   * @param {string} type
60276   *        MediaGroup type
60277   * @param {Object} settings
60278   *        Object containing required information for media groups
60279   * @return {Function}
60280   *         Handler for a non-destructive resync of SegmentLoader when the active media
60281   *         group changes.
60282   * @function onGroupChanged
60283   */
60284
60285
60286  var onGroupChanged = function onGroupChanged(type, settings) {
60287    return function () {
60288      var _settings$segmentLoad = settings.segmentLoaders,
60289          segmentLoader = _settings$segmentLoad[type],
60290          mainSegmentLoader = _settings$segmentLoad.main,
60291          mediaType = settings.mediaTypes[type];
60292      var activeTrack = mediaType.activeTrack();
60293      var activeGroup = mediaType.getActiveGroup();
60294      var previousActiveLoader = mediaType.activePlaylistLoader;
60295      var lastGroup = mediaType.lastGroup_; // the group did not change do nothing
60296
60297      if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
60298        return;
60299      }
60300
60301      mediaType.lastGroup_ = activeGroup;
60302      mediaType.lastTrack_ = activeTrack;
60303      stopLoaders(segmentLoader, mediaType);
60304
60305      if (!activeGroup || activeGroup.isMasterPlaylist) {
60306        // there is no group active or active group is a main playlist and won't change
60307        return;
60308      }
60309
60310      if (!activeGroup.playlistLoader) {
60311        if (previousActiveLoader) {
60312          // The previous group had a playlist loader but the new active group does not
60313          // this means we are switching from demuxed to muxed audio. In this case we want to
60314          // do a destructive reset of the main segment loader and not restart the audio
60315          // loaders.
60316          mainSegmentLoader.resetEverything();
60317        }
60318
60319        return;
60320      } // Non-destructive resync
60321
60322
60323      segmentLoader.resyncLoader();
60324      startLoaders(activeGroup.playlistLoader, mediaType);
60325    };
60326  };
60327
60328  var onGroupChanging = function onGroupChanging(type, settings) {
60329    return function () {
60330      var segmentLoader = settings.segmentLoaders[type],
60331          mediaType = settings.mediaTypes[type];
60332      mediaType.lastGroup_ = null;
60333      segmentLoader.abort();
60334      segmentLoader.pause();
60335    };
60336  };
60337  /**
60338   * Returns a function to be called when the media track changes. It performs a
60339   * destructive reset of the SegmentLoader to ensure we start loading as close to
60340   * currentTime as possible.
60341   *
60342   * @param {string} type
60343   *        MediaGroup type
60344   * @param {Object} settings
60345   *        Object containing required information for media groups
60346   * @return {Function}
60347   *         Handler for a destructive reset of SegmentLoader when the active media
60348   *         track changes.
60349   * @function onTrackChanged
60350   */
60351
60352
60353  var onTrackChanged = function onTrackChanged(type, settings) {
60354    return function () {
60355      var masterPlaylistLoader = settings.masterPlaylistLoader,
60356          _settings$segmentLoad2 = settings.segmentLoaders,
60357          segmentLoader = _settings$segmentLoad2[type],
60358          mainSegmentLoader = _settings$segmentLoad2.main,
60359          mediaType = settings.mediaTypes[type];
60360      var activeTrack = mediaType.activeTrack();
60361      var activeGroup = mediaType.getActiveGroup();
60362      var previousActiveLoader = mediaType.activePlaylistLoader;
60363      var lastTrack = mediaType.lastTrack_; // track did not change, do nothing
60364
60365      if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
60366        return;
60367      }
60368
60369      mediaType.lastGroup_ = activeGroup;
60370      mediaType.lastTrack_ = activeTrack;
60371      stopLoaders(segmentLoader, mediaType);
60372
60373      if (!activeGroup) {
60374        // there is no group active so we do not want to restart loaders
60375        return;
60376      }
60377
60378      if (activeGroup.isMasterPlaylist) {
60379        // track did not change, do nothing
60380        if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
60381          return;
60382        }
60383
60384        var mpc = settings.vhs.masterPlaylistController_;
60385        var newPlaylist = mpc.selectPlaylist(); // media will not change do nothing
60386
60387        if (mpc.media() === newPlaylist) {
60388          return;
60389        }
60390
60391        mediaType.logger_("track change. Switching master audio from " + lastTrack.id + " to " + activeTrack.id);
60392        masterPlaylistLoader.pause();
60393        mainSegmentLoader.resetEverything();
60394        mpc.fastQualityChange_(newPlaylist);
60395        return;
60396      }
60397
60398      if (type === 'AUDIO') {
60399        if (!activeGroup.playlistLoader) {
60400          // when switching from demuxed audio/video to muxed audio/video (noted by no
60401          // playlist loader for the audio group), we want to do a destructive reset of the
60402          // main segment loader and not restart the audio loaders
60403          mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
60404          // it should be stopped
60405
60406          mainSegmentLoader.resetEverything();
60407          return;
60408        } // although the segment loader is an audio segment loader, call the setAudio
60409        // function to ensure it is prepared to re-append the init segment (or handle other
60410        // config changes)
60411
60412
60413        segmentLoader.setAudio(true);
60414        mainSegmentLoader.setAudio(false);
60415      }
60416
60417      if (previousActiveLoader === activeGroup.playlistLoader) {
60418        // Nothing has actually changed. This can happen because track change events can fire
60419        // multiple times for a "single" change. One for enabling the new active track, and
60420        // one for disabling the track that was active
60421        startLoaders(activeGroup.playlistLoader, mediaType);
60422        return;
60423      }
60424
60425      if (segmentLoader.track) {
60426        // For WebVTT, set the new text track in the segmentloader
60427        segmentLoader.track(activeTrack);
60428      } // destructive reset
60429
60430
60431      segmentLoader.resetEverything();
60432      startLoaders(activeGroup.playlistLoader, mediaType);
60433    };
60434  };
60435
60436  var onError = {
60437    /**
60438     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
60439     * an error.
60440     *
60441     * @param {string} type
60442     *        MediaGroup type
60443     * @param {Object} settings
60444     *        Object containing required information for media groups
60445     * @return {Function}
60446     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
60447     *         console and switches back to default audio track.
60448     * @function onError.AUDIO
60449     */
60450    AUDIO: function AUDIO(type, settings) {
60451      return function () {
60452        var segmentLoader = settings.segmentLoaders[type],
60453            mediaType = settings.mediaTypes[type],
60454            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
60455        stopLoaders(segmentLoader, mediaType); // switch back to default audio track
60456
60457        var activeTrack = mediaType.activeTrack();
60458        var activeGroup = mediaType.activeGroup();
60459        var id = (activeGroup.filter(function (group) {
60460          return group["default"];
60461        })[0] || activeGroup[0]).id;
60462        var defaultTrack = mediaType.tracks[id];
60463
60464        if (activeTrack === defaultTrack) {
60465          // Default track encountered an error. All we can do now is blacklist the current
60466          // rendition and hope another will switch audio groups
60467          blacklistCurrentPlaylist({
60468            message: 'Problem encountered loading the default audio track.'
60469          });
60470          return;
60471        }
60472
60473        videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
60474
60475        for (var trackId in mediaType.tracks) {
60476          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
60477        }
60478
60479        mediaType.onTrackChanged();
60480      };
60481    },
60482
60483    /**
60484     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
60485     * an error.
60486     *
60487     * @param {string} type
60488     *        MediaGroup type
60489     * @param {Object} settings
60490     *        Object containing required information for media groups
60491     * @return {Function}
60492     *         Error handler. Logs warning to console and disables the active subtitle track
60493     * @function onError.SUBTITLES
60494     */
60495    SUBTITLES: function SUBTITLES(type, settings) {
60496      return function () {
60497        var segmentLoader = settings.segmentLoaders[type],
60498            mediaType = settings.mediaTypes[type];
60499        videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
60500        stopLoaders(segmentLoader, mediaType);
60501        var track = mediaType.activeTrack();
60502
60503        if (track) {
60504          track.mode = 'disabled';
60505        }
60506
60507        mediaType.onTrackChanged();
60508      };
60509    }
60510  };
60511  var setupListeners = {
60512    /**
60513     * Setup event listeners for audio playlist loader
60514     *
60515     * @param {string} type
60516     *        MediaGroup type
60517     * @param {PlaylistLoader|null} playlistLoader
60518     *        PlaylistLoader to register listeners on
60519     * @param {Object} settings
60520     *        Object containing required information for media groups
60521     * @function setupListeners.AUDIO
60522     */
60523    AUDIO: function AUDIO(type, playlistLoader, settings) {
60524      if (!playlistLoader) {
60525        // no playlist loader means audio will be muxed with the video
60526        return;
60527      }
60528
60529      var tech = settings.tech,
60530          requestOptions = settings.requestOptions,
60531          segmentLoader = settings.segmentLoaders[type];
60532      playlistLoader.on('loadedmetadata', function () {
60533        var media = playlistLoader.media();
60534        segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
60535        // permits, start downloading segments
60536
60537        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
60538          segmentLoader.load();
60539        }
60540      });
60541      playlistLoader.on('loadedplaylist', function () {
60542        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
60543
60544        if (!tech.paused()) {
60545          segmentLoader.load();
60546        }
60547      });
60548      playlistLoader.on('error', onError[type](type, settings));
60549    },
60550
60551    /**
60552     * Setup event listeners for subtitle playlist loader
60553     *
60554     * @param {string} type
60555     *        MediaGroup type
60556     * @param {PlaylistLoader|null} playlistLoader
60557     *        PlaylistLoader to register listeners on
60558     * @param {Object} settings
60559     *        Object containing required information for media groups
60560     * @function setupListeners.SUBTITLES
60561     */
60562    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
60563      var tech = settings.tech,
60564          requestOptions = settings.requestOptions,
60565          segmentLoader = settings.segmentLoaders[type],
60566          mediaType = settings.mediaTypes[type];
60567      playlistLoader.on('loadedmetadata', function () {
60568        var media = playlistLoader.media();
60569        segmentLoader.playlist(media, requestOptions);
60570        segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
60571        // permits, start downloading segments
60572
60573        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
60574          segmentLoader.load();
60575        }
60576      });
60577      playlistLoader.on('loadedplaylist', function () {
60578        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
60579
60580        if (!tech.paused()) {
60581          segmentLoader.load();
60582        }
60583      });
60584      playlistLoader.on('error', onError[type](type, settings));
60585    }
60586  };
60587  var initialize = {
60588    /**
60589     * Setup PlaylistLoaders and AudioTracks for the audio groups
60590     *
60591     * @param {string} type
60592     *        MediaGroup type
60593     * @param {Object} settings
60594     *        Object containing required information for media groups
60595     * @function initialize.AUDIO
60596     */
60597    'AUDIO': function AUDIO(type, settings) {
60598      var vhs = settings.vhs,
60599          sourceType = settings.sourceType,
60600          segmentLoader = settings.segmentLoaders[type],
60601          requestOptions = settings.requestOptions,
60602          mediaGroups = settings.master.mediaGroups,
60603          _settings$mediaTypes$ = settings.mediaTypes[type],
60604          groups = _settings$mediaTypes$.groups,
60605          tracks = _settings$mediaTypes$.tracks,
60606          logger_ = _settings$mediaTypes$.logger_,
60607          masterPlaylistLoader = settings.masterPlaylistLoader;
60608      var audioOnlyMaster = isAudioOnly(masterPlaylistLoader.master); // force a default if we have none
60609
60610      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
60611        mediaGroups[type] = {
60612          main: {
60613            "default": {
60614              "default": true
60615            }
60616          }
60617        };
60618
60619        if (audioOnlyMaster) {
60620          mediaGroups[type].main["default"].playlists = masterPlaylistLoader.master.playlists;
60621        }
60622      }
60623
60624      for (var groupId in mediaGroups[type]) {
60625        if (!groups[groupId]) {
60626          groups[groupId] = [];
60627        }
60628
60629        for (var variantLabel in mediaGroups[type][groupId]) {
60630          var properties = mediaGroups[type][groupId][variantLabel];
60631          var playlistLoader = void 0;
60632
60633          if (audioOnlyMaster) {
60634            logger_("AUDIO group '" + groupId + "' label '" + variantLabel + "' is a master playlist");
60635            properties.isMasterPlaylist = true;
60636            playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
60637            // use the resolved media playlist object
60638          } else if (sourceType === 'vhs-json' && properties.playlists) {
60639            playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
60640          } else if (properties.resolvedUri) {
60641            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
60642            // should we even have properties.playlists in this check.
60643          } else if (properties.playlists && sourceType === 'dash') {
60644            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
60645          } else {
60646            // no resolvedUri means the audio is muxed with the video when using this
60647            // audio track
60648            playlistLoader = null;
60649          }
60650
60651          properties = videojs.mergeOptions({
60652            id: variantLabel,
60653            playlistLoader: playlistLoader
60654          }, properties);
60655          setupListeners[type](type, properties.playlistLoader, settings);
60656          groups[groupId].push(properties);
60657
60658          if (typeof tracks[variantLabel] === 'undefined') {
60659            var track = new videojs.AudioTrack({
60660              id: variantLabel,
60661              kind: audioTrackKind_(properties),
60662              enabled: false,
60663              language: properties.language,
60664              "default": properties["default"],
60665              label: variantLabel
60666            });
60667            tracks[variantLabel] = track;
60668          }
60669        }
60670      } // setup single error event handler for the segment loader
60671
60672
60673      segmentLoader.on('error', onError[type](type, settings));
60674    },
60675
60676    /**
60677     * Setup PlaylistLoaders and TextTracks for the subtitle groups
60678     *
60679     * @param {string} type
60680     *        MediaGroup type
60681     * @param {Object} settings
60682     *        Object containing required information for media groups
60683     * @function initialize.SUBTITLES
60684     */
60685    'SUBTITLES': function SUBTITLES(type, settings) {
60686      var tech = settings.tech,
60687          vhs = settings.vhs,
60688          sourceType = settings.sourceType,
60689          segmentLoader = settings.segmentLoaders[type],
60690          requestOptions = settings.requestOptions,
60691          mediaGroups = settings.master.mediaGroups,
60692          _settings$mediaTypes$2 = settings.mediaTypes[type],
60693          groups = _settings$mediaTypes$2.groups,
60694          tracks = _settings$mediaTypes$2.tracks,
60695          masterPlaylistLoader = settings.masterPlaylistLoader;
60696
60697      for (var groupId in mediaGroups[type]) {
60698        if (!groups[groupId]) {
60699          groups[groupId] = [];
60700        }
60701
60702        for (var variantLabel in mediaGroups[type][groupId]) {
60703          if (mediaGroups[type][groupId][variantLabel].forced) {
60704            // Subtitle playlists with the forced attribute are not selectable in Safari.
60705            // According to Apple's HLS Authoring Specification:
60706            //   If content has forced subtitles and regular subtitles in a given language,
60707            //   the regular subtitles track in that language MUST contain both the forced
60708            //   subtitles and the regular subtitles for that language.
60709            // Because of this requirement and that Safari does not add forced subtitles,
60710            // forced subtitles are skipped here to maintain consistent experience across
60711            // all platforms
60712            continue;
60713          }
60714
60715          var properties = mediaGroups[type][groupId][variantLabel];
60716          var playlistLoader = void 0;
60717
60718          if (sourceType === 'hls') {
60719            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
60720          } else if (sourceType === 'dash') {
60721            var playlists = properties.playlists.filter(function (p) {
60722              return p.excludeUntil !== Infinity;
60723            });
60724
60725            if (!playlists.length) {
60726              return;
60727            }
60728
60729            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
60730          } else if (sourceType === 'vhs-json') {
60731            playlistLoader = new PlaylistLoader( // if the vhs-json object included the media playlist, use the media playlist
60732            // as provided, otherwise use the resolved URI to load the playlist
60733            properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
60734          }
60735
60736          properties = videojs.mergeOptions({
60737            id: variantLabel,
60738            playlistLoader: playlistLoader
60739          }, properties);
60740          setupListeners[type](type, properties.playlistLoader, settings);
60741          groups[groupId].push(properties);
60742
60743          if (typeof tracks[variantLabel] === 'undefined') {
60744            var track = tech.addRemoteTextTrack({
60745              id: variantLabel,
60746              kind: 'subtitles',
60747              "default": properties["default"] && properties.autoselect,
60748              language: properties.language,
60749              label: variantLabel
60750            }, false).track;
60751            tracks[variantLabel] = track;
60752          }
60753        }
60754      } // setup single error event handler for the segment loader
60755
60756
60757      segmentLoader.on('error', onError[type](type, settings));
60758    },
60759
60760    /**
60761     * Setup TextTracks for the closed-caption groups
60762     *
60763     * @param {String} type
60764     *        MediaGroup type
60765     * @param {Object} settings
60766     *        Object containing required information for media groups
60767     * @function initialize['CLOSED-CAPTIONS']
60768     */
60769    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
60770      var tech = settings.tech,
60771          mediaGroups = settings.master.mediaGroups,
60772          _settings$mediaTypes$3 = settings.mediaTypes[type],
60773          groups = _settings$mediaTypes$3.groups,
60774          tracks = _settings$mediaTypes$3.tracks;
60775
60776      for (var groupId in mediaGroups[type]) {
60777        if (!groups[groupId]) {
60778          groups[groupId] = [];
60779        }
60780
60781        for (var variantLabel in mediaGroups[type][groupId]) {
60782          var properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
60783
60784          if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
60785            continue;
60786          }
60787
60788          var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
60789          var newProps = {
60790            label: variantLabel,
60791            language: properties.language,
60792            instreamId: properties.instreamId,
60793            "default": properties["default"] && properties.autoselect
60794          };
60795
60796          if (captionServices[newProps.instreamId]) {
60797            newProps = videojs.mergeOptions(newProps, captionServices[newProps.instreamId]);
60798          }
60799
60800          if (newProps["default"] === undefined) {
60801            delete newProps["default"];
60802          } // No PlaylistLoader is required for Closed-Captions because the captions are
60803          // embedded within the video stream
60804
60805
60806          groups[groupId].push(videojs.mergeOptions({�
60806
60807            id: variantLabel
60808          }, properties));
60809
60810          if (typeof tracks[variantLabel] === 'undefined') {
60811            var track = tech.addRemoteTextTrack({
60812              id: newProps.instreamId,
60813              kind: 'captions',
60814              "default": newProps["default"],
60815              language: newProps.language,
60816              label: newProps.label
60817            }, false).track;
60818            tracks[variantLabel] = track;
60819          }
60820        }
60821      }
60822    }
60823  };
60824
60825  var groupMatch = function groupMatch(list, media) {
60826    for (var i = 0; i < list.length; i++) {
60827      if (playlistMatch(media, list[i])) {
60828        return true;
60829      }
60830
60831      if (list[i].playlists && groupMatch(list[i].playlists, media)) {
60832        return true;
60833      }
60834    }
60835
60836    return false;
60837  };
60838  /**
60839   * Returns a function used to get the active group of the provided type
60840   *
60841   * @param {string} type
60842   *        MediaGroup type
60843   * @param {Object} settings
60844   *        Object containing required information for media groups
60845   * @return {Function}
60846   *         Function that returns the active media group for the provided type. Takes an
60847   *         optional parameter {TextTrack} track. If no track is provided, a list of all
60848   *         variants in the group, otherwise the variant corresponding to the provided
60849   *         track is returned.
60850   * @function activeGroup
60851   */
60852
60853
60854  var activeGroup = function activeGroup(type, settings) {
60855    return function (track) {
60856      var masterPlaylistLoader = settings.masterPlaylistLoader,
60857          groups = settings.mediaTypes[type].groups;
60858      var media = masterPlaylistLoader.media();
60859
60860      if (!media) {
60861        return null;
60862      }
60863
60864      var variants = null; // set to variants to main media active group
60865
60866      if (media.attributes[type]) {
60867        variants = groups[media.attributes[type]];
60868      }
60869
60870      var groupKeys = Object.keys(groups);
60871
60872      if (!variants) {
60873        // find the masterPlaylistLoader media
60874        // that is in a media group if we are dealing
60875        // with audio only
60876        if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.master)) {
60877          for (var i = 0; i < groupKeys.length; i++) {
60878            var groupPropertyList = groups[groupKeys[i]];
60879
60880            if (groupMatch(groupPropertyList, media)) {
60881              variants = groupPropertyList;
60882              break;
60883            }
60884          } // use the main group if it exists
60885
60886        } else if (groups.main) {
60887          variants = groups.main; // only one group, use that one
60888        } else if (groupKeys.length === 1) {
60889          variants = groups[groupKeys[0]];
60890        }
60891      }
60892
60893      if (typeof track === 'undefined') {
60894        return variants;
60895      }
60896
60897      if (track === null || !variants) {
60898        // An active track was specified so a corresponding group is expected. track === null
60899        // means no track is currently active so there is no corresponding group
60900        return null;
60901      }
60902
60903      return variants.filter(function (props) {
60904        return props.id === track.id;
60905      })[0] || null;
60906    };
60907  };
60908
60909  var activeTrack = {
60910    /**
60911     * Returns a function used to get the active track of type provided
60912     *
60913     * @param {string} type
60914     *        MediaGroup type
60915     * @param {Object} settings
60916     *        Object containing required information for media groups
60917     * @return {Function}
60918     *         Function that returns the active media track for the provided type. Returns
60919     *         null if no track is active
60920     * @function activeTrack.AUDIO
60921     */
60922    AUDIO: function AUDIO(type, settings) {
60923      return function () {
60924        var tracks = settings.mediaTypes[type].tracks;
60925
60926        for (var id in tracks) {
60927          if (tracks[id].enabled) {
60928            return tracks[id];
60929          }
60930        }
60931
60932        return null;
60933      };
60934    },
60935
60936    /**
60937     * Returns a function used to get the active track of type provided
60938     *
60939     * @param {string} type
60940     *        MediaGroup type
60941     * @param {Object} settings
60942     *        Object containing required information for media groups
60943     * @return {Function}
60944     *         Function that returns the active media track for the provided type. Returns
60945     *         null if no track is active
60946     * @function activeTrack.SUBTITLES
60947     */
60948    SUBTITLES: function SUBTITLES(type, settings) {
60949      return function () {
60950        var tracks = settings.mediaTypes[type].tracks;
60951
60952        for (var id in tracks) {
60953          if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
60954            return tracks[id];
60955          }
60956        }
60957
60958        return null;
60959      };
60960    }
60961  };
60962
60963  var getActiveGroup = function getActiveGroup(type, _ref) {
60964    var mediaTypes = _ref.mediaTypes;
60965    return function () {
60966      var activeTrack_ = mediaTypes[type].activeTrack();
60967
60968      if (!activeTrack_) {
60969        return null;
60970      }
60971
60972      return mediaTypes[type].activeGroup(activeTrack_);
60973    };
60974  };
60975  /**
60976   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
60977   * Closed-Captions) specified in the master manifest.
60978   *
60979   * @param {Object} settings
60980   *        Object containing required information for setting up the media groups
60981   * @param {Tech} settings.tech
60982   *        The tech of the player
60983   * @param {Object} settings.requestOptions
60984   *        XHR request options used by the segment loaders
60985   * @param {PlaylistLoader} settings.masterPlaylistLoader
60986   *        PlaylistLoader for the master source
60987   * @param {VhsHandler} settings.vhs
60988   *        VHS SourceHandler
60989   * @param {Object} settings.master
60990   *        The parsed master manifest
60991   * @param {Object} settings.mediaTypes
60992   *        Object to store the loaders, tracks, and utility methods for each media type
60993   * @param {Function} settings.blacklistCurrentPlaylist
60994   *        Blacklists the current rendition and forces a rendition switch.
60995   * @function setupMediaGroups
60996   */
60997
60998
60999  var setupMediaGroups = function setupMediaGroups(settings) {
61000    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
61001      initialize[type](type, settings);
61002    });
61003    var mediaTypes = settings.mediaTypes,
61004        masterPlaylistLoader = settings.masterPlaylistLoader,
61005        tech = settings.tech,
61006        vhs = settings.vhs,
61007        _settings$segmentLoad3 = settings.segmentLoaders,
61008        audioSegmentLoader = _settings$segmentLoad3['AUDIO'],
61009        mainSegmentLoader = _settings$segmentLoad3.main; // setup active group and track getters and change event handlers
61010
61011    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
61012      mediaTypes[type].activeGroup = activeGroup(type, settings);
61013      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
61014      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
61015      mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
61016      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
61017      mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
61018    }); // DO NOT enable the default subtitle or caption track.
61019    // DO enable the default audio track
61020
61021    var audioGroup = mediaTypes.AUDIO.activeGroup();
61022
61023    if (audioGroup) {
61024      var groupId = (audioGroup.filter(function (group) {
61025        return group["default"];
61026      })[0] || audioGroup[0]).id;
61027      mediaTypes.AUDIO.tracks[groupId].enabled = true;
61028      mediaTypes.AUDIO.onGroupChanged();
61029      mediaTypes.AUDIO.onTrackChanged();
61030      var activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
61031      // track is changed, but needs to be handled here since the track may not be considered
61032      // changed on the first call to onTrackChanged
61033
61034      if (!activeAudioGroup.playlistLoader) {
61035        // either audio is muxed with video or the stream is audio only
61036        mainSegmentLoader.setAudio(true);
61037      } else {
61038        // audio is demuxed
61039        mainSegmentLoader.setAudio(false);
61040        audioSegmentLoader.setAudio(true);
61041      }
61042    }
61043
61044    masterPlaylistLoader.on('mediachange', function () {
61045      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
61046        return mediaTypes[type].onGroupChanged();
61047      });
61048    });
61049    masterPlaylistLoader.on('mediachanging', function () {
61050      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
61051        return mediaTypes[type].onGroupChanging();
61052      });
61053    }); // custom audio track change event handler for usage event
61054
61055    var onAudioTrackChanged = function onAudioTrackChanged() {
61056      mediaTypes.AUDIO.onTrackChanged();
61057      tech.trigger({
61058        type: 'usage',
61059        name: 'vhs-audio-change'
61060      });
61061      tech.trigger({
61062        type: 'usage',
61063        name: 'hls-audio-change'
61064      });
61065    };
61066
61067    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
61068    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
61069    vhs.on('dispose', function () {
61070      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
61071      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
61072    }); // clear existing audio tracks and add the ones we just created
61073
61074    tech.clearTracks('audio');
61075
61076    for (var id in mediaTypes.AUDIO.tracks) {
61077      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
61078    }
61079  };
61080  /**
61081   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
61082   * media type
61083   *
61084   * @return {Object}
61085   *         Object to store the loaders, tracks, and utility methods for each media type
61086   * @function createMediaTypes
61087   */
61088
61089
61090  var createMediaTypes = function createMediaTypes() {
61091    var mediaTypes = {};
61092    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
61093      mediaTypes[type] = {
61094        groups: {},
61095        tracks: {},
61096        activePlaylistLoader: null,
61097        activeGroup: noop,
61098        activeTrack: noop,
61099        getActiveGroup: noop,
61100        onGroupChanged: noop,
61101        onTrackChanged: noop,
61102        lastTrack_: null,
61103        logger_: logger("MediaGroups[" + type + "]")
61104      };
61105    });
61106    return mediaTypes;
61107  };
61108
61109  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
61110  var Vhs$1; // SegmentLoader stats that need to have each loader's
61111  // values summed to calculate the final value
61112
61113  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
61114
61115  var sumLoaderStat = function sumLoaderStat(stat) {
61116    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
61117  };
61118
61119  var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) {
61120    var currentPlaylist = _ref.currentPlaylist,
61121        nextPlaylist = _ref.nextPlaylist,
61122        forwardBuffer = _ref.forwardBuffer,
61123        bufferLowWaterLine = _ref.bufferLowWaterLine,
61124        bufferHighWaterLine = _ref.bufferHighWaterLine,
61125        duration = _ref.duration,
61126        experimentalBufferBasedABR = _ref.experimentalBufferBasedABR,
61127        log = _ref.log; // we have no other playlist to switch to
61128
61129    if (!nextPlaylist) {
61130      videojs.log.warn('We received no playlist to switch to. Please check your stream.');
61131      return false;
61132    }
61133
61134    var sharedLogLine = "allowing switch " + (currentPlaylist && currentPlaylist.id || 'null') + " -> " + nextPlaylist.id;
61135
61136    if (!currentPlaylist) {
61137      log(sharedLogLine + " as current playlist is not set");
61138      return true;
61139    } // no need to switch if playlist is the same
61140
61141
61142    if (nextPlaylist.id === currentPlaylist.id) {
61143      return false;
61144    } // If the playlist is live, then we want to not take low water line into account.
61145    // This is because in LIVE, the player plays 3 segments from the end of the
61146    // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
61147    // in those segments, a viewer will never experience a rendition upswitch.
61148
61149
61150    if (!currentPlaylist.endList) {
61151      log(sharedLogLine + " as current playlist is live");
61152      return true;
61153    }
61154
61155    var maxBufferLowWaterLine = experimentalBufferBasedABR ? Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE : Config.MAX_BUFFER_LOW_WATER_LINE; // For the same reason as LIVE, we ignore the low water line when the VOD
61156    // duration is below the max potential low water line
61157
61158    if (duration < maxBufferLowWaterLine) {
61159      log(sharedLogLine + " as duration < max low water line (" + duration + " < " + maxBufferLowWaterLine + ")");
61160      return true;
61161    }
61162
61163    var nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
61164    var currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
61165    // we can switch down
61166
61167    if (nextBandwidth < currBandwidth && (!experimentalBufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
61168      var logLine = sharedLogLine + " as next bandwidth < current bandwidth (" + nextBandwidth + " < " + currBandwidth + ")";
61169
61170      if (experimentalBufferBasedABR) {
61171        logLine += " and forwardBuffer < bufferHighWaterLine (" + forwardBuffer + " < " + bufferHighWaterLine + ")";
61172      }
61173
61174      log(logLine);
61175      return true;
61176    } // and if our buffer is higher than the low water line,
61177    // we can switch up
61178
61179
61180    if ((!experimentalBufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
61181      var _logLine = sharedLogLine + " as forwardBuffer >= bufferLowWaterLine (" + forwardBuffer + " >= " + bufferLowWaterLine + ")";
61182
61183      if (experimentalBufferBasedABR) {
61184        _logLine += " and next bandwidth > current bandwidth (" + nextBandwidth + " > " + currBandwidth + ")";
61185      }
61186
61187      log(_logLine);
61188      return true;
61189    }
61190
61191    log("not " + sharedLogLine + " as no switching criteria met");
61192    return false;
61193  };
61194  /**
61195   * the master playlist controller controller all interactons
61196   * between playlists and segmentloaders. At this time this mainly
61197   * involves a master playlist and a series of audio playlists
61198   * if they are available
61199   *
61200   * @class MasterPlaylistController
61201   * @extends videojs.EventTarget
61202   */
61203
61204
61205  var MasterPlaylistController = /*#__PURE__*/function (_videojs$EventTarget) {
61206    inheritsLoose(MasterPlaylistController, _videojs$EventTarget);
61207
61208    function MasterPlaylistController(options) {
61209      var _this;
61210
61211      _this = _videojs$EventTarget.call(this) || this;
61212      var src = options.src,
61213          handleManifestRedirects = options.handleManifestRedirects,
61214          withCredentials = options.withCredentials,
61215          tech = options.tech,
61216          bandwidth = options.bandwidth,
61217          externVhs = options.externVhs,
61218          useCueTags = options.useCueTags,
61219          blacklistDuration = options.blacklistDuration,
61220          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
61221          sourceType = options.sourceType,
61222          cacheEncryptionKeys = options.cacheEncryptionKeys,
61223          experimentalBufferBasedABR = options.experimentalBufferBasedABR,
61224          experimentalLeastPixelDiffSelector = options.experimentalLeastPixelDiffSelector;
61225
61226      if (!src) {
61227        throw new Error('A non-empty playlist URL or JSON manifest string is required');
61228      }
61229
61230      var maxPlaylistRetries = options.maxPlaylistRetries;
61231
61232      if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
61233        maxPlaylistRetries = Infinity;
61234      }
61235
61236      Vhs$1 = externVhs;
61237      _this.experimentalBufferBasedABR = Boolean(experimentalBufferBasedABR);
61238      _this.experimentalLeastPixelDiffSelector = Boolean(experimentalLeastPixelDiffSelector);
61239      _this.withCredentials = withCredentials;
61240      _this.tech_ = tech;
61241      _this.vhs_ = tech.vhs;
61242      _this.sourceType_ = sourceType;
61243      _this.useCueTags_ = useCueTags;
61244      _this.blacklistDuration = blacklistDuration;
61245      _this.maxPlaylistRetries = maxPlaylistRetries;
61246      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
61247
61248      if (_this.useCueTags_) {
61249        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
61250        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
61251      }
61252
61253      _this.requestOptions_ = {
61254        withCredentials: withCredentials,
61255        handleManifestRedirects: handleManifestRedirects,
61256        maxPlaylistRetries: maxPlaylistRetries,
61257        timeout: null
61258      };
61259
61260      _this.on('error', _this.pauseLoading);
61261
61262      _this.mediaTypes_ = createMediaTypes();
61263      _this.mediaSource = new window_1.MediaSource();
61264      _this.handleDurationChange_ = _this.handleDurationChange_.bind(assertThisInitialized(_this));
61265      _this.handleSourceOpen_ = _this.handleSourceOpen_.bind(assertThisInitialized(_this));
61266      _this.handleSourceEnded_ = _this.handleSourceEnded_.bind(assertThisInitialized(_this));
61267
61268      _this.mediaSource.addEventListener('durationchange', _this.handleDurationChange_); // load the media source into the player
61269
61270
61271      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_);
61272
61273      _this.mediaSource.addEventListener('sourceended', _this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
61274      // everything, and the MediaSource should not be detached without a proper disposal
61275
61276
61277      _this.seekable_ = videojs.createTimeRanges();
61278      _this.hasPlayed_ = false;
61279      _this.syncController_ = new SyncController(options);
61280      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
61281        kind: 'metadata',
61282        label: 'segment-metadata'
61283      }, false).track;
61284      _this.decrypter_ = new Decrypter();
61285      _this.sourceUpdater_ = new SourceUpdater(_this.mediaSource);
61286      _this.inbandTextTracks_ = {};
61287      _this.timelineChangeController_ = new TimelineChangeController();
61288      var segmentLoaderSettings = {
61289        vhs: _this.vhs_,
61290        parse708captions: options.parse708captions,
61291        mediaSource: _this.mediaSource,
61292        currentTime: _this.tech_.currentTime.bind(_this.tech_),
61293        seekable: function seekable() {
61294          return _this.seekable();
61295        },
61296        seeking: function seeking() {
61297          return _this.tech_.seeking();
61298        },
61299        duration: function duration() {
61300          return _this.duration();
61301        },
61302        hasPlayed: function hasPlayed() {
61303          return _this.hasPlayed_;
61304        },
61305        goalBufferLength: function goalBufferLength() {
61306          return _this.goalBufferLength();
61307        },
61308        bandwidth: bandwidth,
61309        syncController: _this.syncController_,
61310        decrypter: _this.decrypter_,
61311        sourceType: _this.sourceType_,
61312        inbandTextTracks: _this.inbandTextTracks_,
61313        cacheEncryptionKeys: cacheEncryptionKeys,
61314        sourceUpdater: _this.sourceUpdater_,
61315        timelineChangeController: _this.timelineChangeController_,
61316        experimentalExactManifestTimings: options.experimentalExactManifestTimings
61317      }; // The source type check not only determines whether a special DASH playlist loader
61318      // should be used, but also covers the case where the provided src is a vhs-json
61319      // manifest object (instead of a URL). In the case of vhs-json, the default
61320      // PlaylistLoader should be used.
61321
61322      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, _this.vhs_, _this.requestOptions_) : new PlaylistLoader(src, _this.vhs_, _this.requestOptions_);
61323
61324      _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
61325      // combined audio/video or just video when alternate audio track is selected
61326
61327
61328      _this.mainSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
61329        segmentMetadataTrack: _this.segmentMetadataTrack_,
61330        loaderType: 'main'
61331      }), options); // alternate audio track
61332
61333      _this.audioSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
61334        loaderType: 'audio'
61335      }), options);
61336      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
61337        loaderType: 'vtt',
61338        featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks
61339      }), options);
61340
61341      _this.setupSegmentLoaderListeners_();
61342
61343      if (_this.experimentalBufferBasedABR) {
61344        _this.masterPlaylistLoader_.one('loadedplaylist', function () {
61345          return _this.startABRTimer_();
61346        });
61347
61348        _this.tech_.on('pause', function () {
61349          return _this.stopABRTimer_();
61350        });
61351
61352        _this.tech_.on('play', function () {
61353          return _this.startABRTimer_();
61354        });
61355      } // Create SegmentLoader stat-getters
61356      // mediaRequests_
61357      // mediaRequestsAborted_
61358      // mediaRequestsTimedout_
61359      // mediaRequestsErrored_
61360      // mediaTransferDuration_
61361      // mediaBytesTransferred_
61362      // mediaAppends_
61363
61364
61365      loaderStats.forEach(function (stat) {
61366        _this[stat + '_'] = sumLoaderStat.bind(assertThisInitialized(_this), stat);
61367      });
61368      _this.logger_ = logger('MPC');
61369      _this.triggeredFmp4Usage = false;
61370
61371      if (_this.tech_.preload() === 'none') {
61372        _this.loadOnPlay_ = function () {
61373          _this.loadOnPlay_ = null;
61374
61375          _this.masterPlaylistLoader_.load();
61376        };
61377
61378        _this.tech_.one('play', _this.loadOnPlay_);
61379      } else {
61380        _this.masterPlaylistLoader_.load();
61381      }
61382
61383      _this.timeToLoadedData__ = -1;
61384      _this.mainAppendsToLoadedData__ = -1;
61385      _this.audioAppendsToLoadedData__ = -1;
61386      var event = _this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
61387
61388      _this.tech_.one(event, function () {
61389        var timeToLoadedDataStart = Date.now();
61390
61391        _this.tech_.one('loadeddata', function () {
61392          _this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
61393          _this.mainAppendsToLoadedData__ = _this.mainSegmentLoader_.mediaAppends;
61394          _this.audioAppendsToLoadedData__ = _this.audioSegmentLoader_.mediaAppends;
61395        });
61396      });
61397
61398      return _this;
61399    }
61400
61401    var _proto = MasterPlaylistController.prototype;
61402
61403    _proto.mainAppendsToLoadedData_ = function mainAppendsToLoadedData_() {
61404      return this.mainAppendsToLoadedData__;
61405    };
61406
61407    _proto.audioAppendsToLoadedData_ = function audioAppendsToLoadedData_() {
61408      return this.audioAppendsToLoadedData__;
61409    };
61410
61411    _proto.appendsToLoadedData_ = function appendsToLoadedData_() {
61412      var main = this.mainAppendsToLoadedData_();
61413      var audio = this.audioAppendsToLoadedData_();
61414
61415      if (main === -1 || audio === -1) {
61416        return -1;
61417      }
61418
61419      return main + audio;
61420    };
61421
61422    _proto.timeToLoadedData_ = function timeToLoadedData_() {
61423      return this.timeToLoadedData__;
61424    }
61425    /**
61426     * Run selectPlaylist and switch to the new playlist if we should
61427     *
61428     * @private
61429     *
61430     */
61431    ;
61432
61433    _proto.checkABR_ = function checkABR_() {
61434      var nextPlaylist = this.selectPlaylist();
61435
61436      if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
61437        this.switchMedia_(nextPlaylist, 'abr');
61438      }
61439    };
61440
61441    _proto.switchMedia_ = function switchMedia_(playlist, cause, delay) {
61442      var oldMedia = this.media();
61443      var oldId = oldMedia && (oldMedia.id || oldMedia.uri);
61444      var newId = playlist.id || playlist.uri;
61445
61446      if (oldId && oldId !== newId) {
61447        this.logger_("switch media " + oldId + " -> " + newId + " from " + cause);
61448        this.tech_.trigger({
61449          type: 'usage',
61450          name: "vhs-rendition-change-" + cause
61451        });
61452      }
61453
61454      this.masterPlaylistLoader_.media(playlist, delay);
61455    }
61456    /**
61457     * Start a timer that periodically calls checkABR_
61458     *
61459     * @private
61460     */
61461    ;
61462
61463    _proto.startABRTimer_ = function startABRTimer_() {
61464      var _this2 = this;
61465
61466      this.stopABRTimer_();
61467      this.abrTimer_ = window_1.setInterval(function () {
61468        return _this2.checkABR_();
61469      }, 250);
61470    }
61471    /**
61472     * Stop the timer that periodically calls checkABR_
61473     *
61474     * @private
61475     */
61476    ;
61477
61478    _proto.stopABRTimer_ = function stopABRTimer_() {
61479      // if we're scrubbing, we don't need to pause.
61480      // This getter will be added to Video.js in version 7.11.
61481      if (this.tech_.scrubbing && this.tech_.scrubbing()) {
61482        return;
61483      }
61484
61485      window_1.clearInterval(this.abrTimer_);
61486      this.abrTimer_ = null;
61487    }
61488    /**
61489     * Get a list of playlists for the currently selected audio playlist
61490     *
61491     * @return {Array} the array of audio playlists
61492     */
61493    ;
61494
61495    _proto.getAudioTrackPlaylists_ = function getAudioTrackPlaylists_() {
61496      var master = this.master();
61497      var defaultPlaylists = master && master.playlists || []; // if we don't have any audio groups then we can only
61498      // assume that the audio tracks are contained in masters
61499      // playlist array, use that or an empty array.
61500
61501      if (!master || !master.mediaGroups || !master.mediaGroups.AUDIO) {
61502        return defaultPlaylists;
61503      }
61504
61505      var AUDIO = master.mediaGroups.AUDIO;
61506      var groupKeys = Object.keys(AUDIO);
61507      var track; // get the current active track
61508
61509      if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
61510        track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from master if mediaTypes_ isn't setup yet
61511      } else {
61512        // default group is `main` or just the first group.
61513        var defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
61514
61515        for (var label in defaultGroup) {
61516          if (defaultGroup[label]["default"]) {
61517            track = {
61518              label: label
61519            };
61520            break;
61521          }
61522        }
61523      } // no active track no playlists.
61524
61525
61526      if (!track) {
61527        return defaultPlaylists;
61528      }
61529
61530      var playlists = []; // get all of the playlists that are possible for the
61531      // active track.
61532
61533      for (var group in AUDIO) {
61534        if (AUDIO[group][track.label]) {
61535          var properties = AUDIO[group][track.label];
61536
61537          if (properties.playlists && properties.playlists.length) {
61538            playlists.push.apply(playlists, properties.playlists);
61539          } else if (properties.uri) {
61540            playlists.push(properties);
61541          } else if (master.playlists.length) {
61542            // if an audio group does not have a uri
61543            // see if we have main playlists that use it as a group.
61544            // if we do then add those to the playlists list.
61545            for (var i = 0; i < master.playlists.length; i++) {
61546              var playlist = master.playlists[i];
61547
61548              if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
61549                playlists.push(playlist);
61550              }
61551            }
61552          }
61553        }
61554      }
61555
61556      if (!playlists.length) {
61557        return defaultPlaylists;
61558      }
61559
61560      return playlists;
61561    }
61562    /**
61563     * Register event handlers on the master playlist loader. A helper
61564     * function for construction time.
61565     *
61566     * @private
61567     */
61568    ;
61569
61570    _proto.setupMasterPlaylistLoaderListeners_ = function setupMasterPlaylistLoaderListeners_() {
61571      var _this3 = this;
61572
61573      this.masterPlaylistLoader_.on('loadedmetadata', function () {
61574        var media = _this3.masterPlaylistLoader_.media();
61575
61576        var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61577        // timeout the request.
61578
61579        if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) {
61580          _this3.requestOptions_.timeout = 0;
61581        } else {
61582          _this3.requestOptions_.timeout = requestTimeout;
61583        } // if this isn't a live video and preload permits, start
61584        // downloading segments
61585
61586
61587        if (media.endList && _this3.tech_.preload() !== 'none') {
61588          _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_);
61589
61590          _this3.mainSegmentLoader_.load();
61591        }
61592
61593        setupMediaGroups({
61594          sourceType: _this3.sourceType_,
61595          segmentLoaders: {
61596            AUDIO: _this3.audioSegmentLoader_,
61597            SUBTITLES: _this3.subtitleSegmentLoader_,
61598            main: _this3.mainSegmentLoader_
61599          },
61600          tech: _this3.tech_,
61601          requestOptions: _this3.requestOptions_,
61602          masterPlaylistLoader: _this3.masterPlaylistLoader_,
61603          vhs: _this3.vhs_,
61604          master: _this3.master(),
61605          mediaTypes: _this3.mediaTypes_,
61606          blacklistCurrentPlaylist: _this3.blacklistCurrentPlaylist.bind(_this3)
61607        });
61608
61609        _this3.triggerPresenceUsage_(_this3.master(), media);
61610
61611        _this3.setupFirstPlay();
61612
61613        if (!_this3.mediaTypes_.AUDIO.activePlaylistLoader || _this3.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
61614          _this3.trigger('selectedinitialmedia');
61615        } else {
61616          // We must wait for the active audio playlist loader to
61617          // finish setting up before triggering this event so the
61618          // representations API and EME setup is correct
61619          _this3.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
61620            _this3.trigger('selectedinitialmedia');
61621          });
61622        }
61623      });
61624      this.masterPlaylistLoader_.on('loadedplaylist', function () {
61625        if (_this3.loadOnPlay_) {
61626          _this3.tech_.off('play', _this3.loadOnPlay_);
61627        }
61628
61629        var updatedPlaylist = _this3.masterPlaylistLoader_.media();
61630
61631        if (!updatedPlaylist) {
61632          // exclude any variants that are not supported by the browser before selecting
61633          // an initial media as the playlist selectors do not consider browser support
61634          _this3.excludeUnsupportedVariants_();
61635
61636          var selectedMedia;
61637
61638          if (_this3.enableLowInitialPlaylist) {
61639            selectedMedia = _this3.selectInitialPlaylist();
61640          }
61641
61642          if (!selectedMedia) {
61643            selectedMedia = _this3.selectPlaylist();
61644          }
61645
61646          if (!selectedMedia || !_this3.shouldSwitchToMedia_(selectedMedia)) {
61647            return;
61648          }
61649
61650          _this3.initialMedia_ = selectedMedia;
61651
61652          _this3.switchMedia_(_this3.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
61653          // fire again since the playlist will be requested. In the case of vhs-json
61654          // (where the manifest object is provided as the source), when the media
61655          // playlist's `segments` list is already available, a media playlist won't be
61656          // requested, and loadedplaylist won't fire again, so the playlist handler must be
61657          // called on its own here.
61658
61659
61660          var haveJsonSource = _this3.sourceType_ === 'vhs-json' && _this3.initialMedia_.segments;
61661
61662          if (!haveJsonSource) {
61663            return;
61664          }
61665
61666          updatedPlaylist = _this3.initialMedia_;
61667        }
61668
61669        _this3.handleUpdatedMediaPlaylist(updatedPlaylist);
61670      });
61671      this.masterPlaylistLoader_.on('error', function () {
61672        _this3.blacklistCurrentPlaylist(_this3.masterPlaylistLoader_.error);
61673      });
61674      this.masterPlaylistLoader_.on('mediachanging', function () {
61675        _this3.mainSegmentLoader_.abort();
61676
61677        _this3.mainSegmentLoader_.pause();
61678      });
61679      this.masterPlaylistLoader_.on('mediachange', function () {
61680        var media = _this3.masterPlaylistLoader_.media();
61681
61682        var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61683        // timeout the request.
61684
61685        if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) {
61686          _this3.requestOptions_.timeout = 0;
61687        } else {
61688          _this3.requestOptions_.timeout = requestTimeout;
61689        } // TODO: Create a new event on the PlaylistLoader that signals
61690        // that the segments have changed in some way and use that to
61691        // update the SegmentLoader instead of doing it twice here and
61692        // on `loadedplaylist`
61693
61694
61695        _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_);
61696
61697        _this3.mainSegmentLoader_.load();
61698
61699        _this3.tech_.trigger({
61700          type: 'mediachange',
61701          bubbles: true
61702        });
61703      });
61704      this.masterPlaylistLoader_.on('playlistunchanged', function () {
61705        var updatedPlaylist = _this3.masterPlaylistLoader_.media(); // ignore unchanged playlists that have already been
61706        // excluded for not-changing. We likely just have a really slowly updating
61707        // playlist.
61708
61709
61710        if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
61711          return;
61712        }
61713
61714        var playlistOutdated = _this3.stuckAtPlaylistEnd_(updatedPlaylist);
61715
61716        if (playlistOutdated) {
61717          // Playlist has stopped updating and we're stuck at its end. Try to
61718          // blacklist it and switch to another playlist in the hope that that
61719          // one is updating (and give the player a chance to re-adjust to the
61720          // safe live point).
61721          _this3.blacklistCurrentPlaylist({
61722            message: 'Playlist no longer updating.',
61723            reason: 'playlist-unchanged'
61724          }); // useful for monitoring QoS
61725
61726
61727          _this3.tech_.trigger('playliststuck');
61728        }
61729      });
61730      this.masterPlaylistLoader_.on('renditiondisabled', function () {
61731        _this3.tech_.trigger({
61732          type: 'usage',
61733          name: 'vhs-rendition-disabled'
61734        });
61735
61736        _this3.tech_.trigger({
61737          type: 'usage',
61738          name: 'hls-rendition-disabled'
61739        });
61740      });
61741      this.masterPlaylistLoader_.on('renditionenabled', function () {
61742        _this3.tech_.trigger({
61743          type: 'usage',
61744          name: 'vhs-rendition-enabled'
61745        });
61746
61747        _this3.tech_.trigger({
61748          type: 'usage',
61749          name: 'hls-rendition-enabled'
61750        });
61751      });
61752    }
61753    /**
61754     * Given an updated media playlist (whether it was loaded for the first time, or
61755     * refreshed for live playlists), update any relevant properties and state to reflect
61756     * changes in the media that should be accounted for (e.g., cues and duration).
61757     *
61758     * @param {Object} updatedPlaylist the updated media playlist object
61759     *
61760     * @private
61761     */
61762    ;
61763
61764    _proto.handleUpdatedMediaPlaylist = function handleUpdatedMediaPlaylist(updatedPlaylist) {
61765      if (this.useCueTags_) {
61766        this.updateAdCues_(updatedPlaylist);
61767      } // TODO: Create a new event on the PlaylistLoader that signals
61768      // that the segments have changed in some way and use that to
61769      // update the SegmentLoader instead of doing it twice here and
61770      // on `mediachange`
61771
61772
61773      this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
61774      this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
61775      // as it is possible that it was temporarily stopped while waiting for
61776      // a playlist (e.g., in case the playlist errored and we re-requested it).
61777
61778      if (!this.tech_.paused()) {
61779        this.mainSegmentLoader_.load();
61780
61781        if (this.audioSegmentLoader_) {
61782          this.audioSegmentLoader_.load();
61783        }
61784      }
61785    }
61786    /**
61787     * A helper function for triggerring presence usage events once per source
61788     *
61789     * @private
61790     */
61791    ;
61792
61793    _proto.triggerPresenceUsage_ = function triggerPresenceUsage_(master, media) {
61794      var mediaGroups = master.mediaGroups || {};
61795      var defaultDemuxed = true;
61796      var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
61797
61798      for (var mediaGroup in mediaGroups.AUDIO) {
61799        for (var label in mediaGroups.AUDIO[mediaGroup]) {
61800          var properties = mediaGroups.AUDIO[mediaGroup][label];
61801
61802          if (!properties.uri) {
61803            defaultDemuxed = false;
61804          }
61805        }
61806      }
61807
61808      if (defaultDemuxed) {
61809        this.tech_.trigger({
61810          type: 'usage',
61811          name: 'vhs-demuxed'
61812        });
61813        this.tech_.trigger({
61814          type: 'usage',
61815          name: 'hls-demuxed'
61816        });
61817      }
61818
61819      if (Object.keys(mediaGroups.SUBTITLES).length) {
61820        this.tech_.trigger({
61821          type: 'usage',
61822          name: 'vhs-webvtt'
61823        });
61824        this.tech_.trigger({
61825          type: 'usage',
61826          name: 'hls-webvtt'
61827        });
61828      }
61829
61830      if (Vhs$1.Playlist.isAes(media)) {
61831        this.tech_.trigger({
61832          type: 'usage',
61833          name: 'vhs-aes'
61834        });
61835        this.tech_.trigger({
61836          type: 'usage',
61837          name: 'hls-aes'
61838        });
61839      }
61840
61841      if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
61842        this.tech_.trigger({
61843          type: 'usage',
61844          name: 'vhs-alternate-audio'
61845        });
61846        this.tech_.trigger({
61847          type: 'usage',
61848          name: 'hls-alternate-audio'
61849        });
61850      }
61851
61852      if (this.useCueTags_) {
61853        this.tech_.trigger({
61854          type: 'usage',
61855          name: 'vhs-playlist-cue-tags'
61856        });
61857        this.tech_.trigger({
61858          type: 'usage',
61859          name: 'hls-playlist-cue-tags'
61860        });
61861      }
61862    };
61863
61864    _proto.shouldSwitchToMedia_ = function shouldSwitchToMedia_(nextPlaylist) {
61865      var currentPlaylist = this.masterPlaylistLoader_.media();
61866      var buffered = this.tech_.buffered();
61867      var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - this.tech_.currentTime() : 0;
61868      var bufferLowWaterLine = this.bufferLowWaterLine();
61869      var bufferHighWaterLine = this.bufferHighWaterLine();
61870      return shouldSwitchToMedia({
61871        currentPlaylist: currentPlaylist,
61872        nextPlaylist: nextPlaylist,
61873        forwardBuffer: forwardBuffer,
61874        bufferLowWaterLine: bufferLowWaterLine,
61875        bufferHighWaterLine: bufferHighWaterLine,
61876        duration: this.duration(),
61877        experimentalBufferBasedABR: this.experimentalBufferBasedABR,
61878        log: this.logger_
61879      });
61880    }
61881    /**
61882     * Register event handlers on the segment loaders. A helper function
61883     * for construction time.
61884     *
61885     * @private
61886     */
61887    ;
61888
61889    _proto.setupSegmentLoaderListeners_ = function setupSegmentLoaderListeners_() {
61890      var _this4 = this;
61891
61892      if (!this.experimentalBufferBasedABR) {
61893        this.mainSegmentLoader_.on('bandwidthupdate', function () {
61894          var nextPlaylist = _this4.selectPlaylist();
61895
61896          if (_this4.shouldSwitchToMedia_(nextPlaylist)) {
61897            _this4.switchMedia_(nextPlaylist, 'bandwidthupdate');
61898          }
61899
61900          _this4.tech_.trigger('bandwidthupdate');
61901        });
61902        this.mainSegmentLoader_.on('progress', function () {
61903          _this4.trigger('progress');
61904        });
61905      }
61906
61907      this.mainSegmentLoader_.on('error', function () {
61908        _this4.blacklistCurrentPlaylist(_this4.mainSegmentLoader_.error());
61909      });
61910      this.mainSegmentLoader_.on('appenderror', function () {
61911        _this4.error = _this4.mainSegmentLoader_.error_;
61912
61913        _this4.trigger('error');
61914      });
61915      this.mainSegmentLoader_.on('syncinfoupdate', function () {
61916        _this4.onSyncInfoUpdate_();
61917      });
61918      this.mainSegmentLoader_.on('timestampoffset', function () {
61919        _this4.tech_.trigger({
61920          type: 'usage',
61921          name: 'vhs-timestamp-offset'
61922        });
61923
61924        _this4.tech_.trigger({
61925          type: 'usage',
61926          name: 'hls-timestamp-offset'
61927        });
61928      });
61929      this.audioSegmentLoader_.on('syncinfoupdate', function () {
61930        _this4.onSyncInfoUpdate_();
61931      });
61932      this.audioSegmentLoader_.on('appenderror', function () {
61933        _this4.error = _this4.audioSegmentLoader_.error_;
61934
61935        _this4.trigger('error');
61936      });
61937      this.mainSegmentLoader_.on('ended', function () {
61938        _this4.logger_('main segment loader ended');
61939
61940        _this4.onEndOfStream();
61941      });
61942      this.mainSegmentLoader_.on('earlyabort', function (event) {
61943        // never try to early abort with the new ABR algorithm
61944        if (_this4.experimentalBufferBasedABR) {
61945          return;
61946        }
61947
61948        _this4.delegateLoaders_('all', ['abort']);
61949
61950        _this4.blacklistCurrentPlaylist({
61951          message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
61952        }, ABORT_EARLY_BLACKLIST_SECONDS);
61953      });
61954
61955      var updateCodecs = function updateCodecs() {
61956        if (!_this4.sourceUpdater_.hasCreatedSourceBuffers()) {
61957          return _this4.tryToCreateSourceBuffers_();
61958        }
61959
61960        var codecs = _this4.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
61961
61962
61963        if (!codecs) {
61964          return;
61965        }
61966
61967        _this4.sourceUpdater_.addOrChangeSourceBuffers(codecs);
61968      };
61969
61970      this.mainSegmentLoader_.on('trackinfo', updateCodecs);
61971      this.audioSegmentLoader_.on('trackinfo', updateCodecs);
61972      this.mainSegmentLoader_.on('fmp4', function () {
61973        if (!_this4.triggeredFmp4Usage) {
61974          _this4.tech_.trigger({
61975            type: 'usage',
61976            name: 'vhs-fmp4'
61977          });
61978
61979          _this4.tech_.trigger({
61980            type: 'usage',
61981            name: 'hls-fmp4'
61982          });
61983
61984          _this4.triggeredFmp4Usage = true;
61985        }
61986      });
61987      this.audioSegmentLoader_.on('fmp4', function () {
61988        if (!_this4.triggeredFmp4Usage) {
61989          _this4.tech_.trigger({
61990            type: 'usage',
61991            name: 'vhs-fmp4'
61992          });
61993
61994          _this4.tech_.trigger({
61995            type: 'usage',
61996            name: 'hls-fmp4'
61997          });
61998
61999          _this4.triggeredFmp4Usage = true;
62000        }
62001      });
62002      this.audioSegmentLoader_.on('ended', function () {
62003        _this4.logger_('audioSegmentLoader ended');
62004
62005        _this4.onEndOfStream();
62006      });
62007    };
62008
62009    _proto.mediaSecondsLoaded_ = function mediaSecondsLoaded_() {
62010      return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
62011    }
62012    /**
62013     * Call load on our SegmentLoaders
62014     */
62015    ;
62016
62017    _proto.load = function load() {
62018      this.mainSegmentLoader_.load();
62019
62020      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
62021        this.audioSegmentLoader_.load();
62022      }
62023
62024      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
62025        this.subtitleSegmentLoader_.load();
62026      }
62027    }
62028    /**
62029     * Re-tune playback quality level for the current player
62030     * conditions without performing destructive actions, like
62031     * removing already buffered content
62032     *
62033     * @private
62034     * @deprecated
62035     */
62036    ;
62037
62038    _proto.smoothQualityChange_ = function smoothQualityChange_(media) {
62039      if (media === void 0) {
62040        media = this.selectPlaylist();
62041      }
62042
62043      this.fastQualityChange_(media);
62044    }
62045    /**
62046     * Re-tune playback quality level for the current player
62047     * conditions. This method will perform destructive actions like remov
62047ing
62048     * already buffered content in order to readjust the currently active
62049     * playlist quickly. This is good for manual quality changes
62050     *
62051     * @private
62052     */
62053    ;
62054
62055    _proto.fastQualityChange_ = function fastQualityChange_(media) {
62056      var _this5 = this;
62057
62058      if (media === void 0) {
62059        media = this.selectPlaylist();
62060      }
62061
62062      if (media === this.masterPlaylistLoader_.media()) {
62063        this.logger_('skipping fastQualityChange because new media is same as old');
62064        return;
62065      }
62066
62067      this.switchMedia_(media, 'fast-quality'); // Delete all buffered data to allow an immediate quality switch, then seek to give
62068      // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
62069      // ahead is roughly the minimum that will accomplish this across a variety of content
62070      // in IE and Edge, but seeking in place is sufficient on all other browsers)
62071      // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
62072      // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
62073
62074      this.mainSegmentLoader_.resetEverything(function () {
62075        // Since this is not a typical seek, we avoid the seekTo method which can cause segments
62076        // from the previously enabled rendition to load before the new playlist has finished loading
62077        if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) {
62078          _this5.tech_.setCurrentTime(_this5.tech_.currentTime() + 0.04);
62079        } else {
62080          _this5.tech_.setCurrentTime(_this5.tech_.currentTime());
62081        }
62082      }); // don't need to reset audio as it is reset when media changes
62083    }
62084    /**
62085     * Begin playback.
62086     */
62087    ;
62088
62089    _proto.play = function play() {
62090      if (this.setupFirstPlay()) {
62091        return;
62092      }
62093
62094      if (this.tech_.ended()) {
62095        this.tech_.setCurrentTime(0);
62096      }
62097
62098      if (this.hasPlayed_) {
62099        this.load();
62100      }
62101
62102      var seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
62103      // seek forward to the live point
62104
62105      if (this.tech_.duration() === Infinity) {
62106        if (this.tech_.currentTime() < seekable.start(0)) {
62107          return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
62108        }
62109      }
62110    }
62111    /**
62112     * Seek to the latest media position if this is a live video and the
62113     * player and video are loaded and initialized.
62114     */
62115    ;
62116
62117    _proto.setupFirstPlay = function setupFirstPlay() {
62118      var _this6 = this;
62119
62120      var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
62121      //  If 1) there is no active media
62122      //     2) the player is paused
62123      //     3) the first play has already been setup
62124      // then exit early
62125
62126      if (!media || this.tech_.paused() || this.hasPlayed_) {
62127        return false;
62128      } // when the video is a live stream
62129
62130
62131      if (!media.endList) {
62132        var seekable = this.seekable();
62133
62134        if (!seekable.length) {
62135          // without a seekable range, the player cannot seek to begin buffering at the live
62136          // point
62137          return false;
62138        }
62139
62140        if (videojs.browser.IE_VERSION && this.tech_.readyState() === 0) {
62141          // IE11 throws an InvalidStateError if you try to set currentTime while the
62142          // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
62143          this.tech_.one('loadedmetadata', function () {
62144            _this6.trigger('firstplay');
62145
62146            _this6.tech_.setCurrentTime(seekable.end(0));
62147
62148            _this6.hasPlayed_ = true;
62149          });
62150          return false;
62151        } // trigger firstplay to inform the source handler to ignore the next seek event
62152
62153
62154        this.trigger('firstplay'); // seek to the live point
62155
62156        this.tech_.setCurrentTime(seekable.end(0));
62157      }
62158
62159      this.hasPlayed_ = true; // we can begin loading now that everything is ready
62160
62161      this.load();
62162      return true;
62163    }
62164    /**
62165     * handle the sourceopen event on the MediaSource
62166     *
62167     * @private
62168     */
62169    ;
62170
62171    _proto.handleSourceOpen_ = function handleSourceOpen_() {
62172      // Only attempt to create the source buffer if none already exist.
62173      // handleSourceOpen is also called when we are "re-opening" a source buffer
62174      // after `endOfStream` has been called (in response to a seek for instance)
62175      this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
62176      // code in video.js but is required because play() must be invoked
62177      // *after* the media source has opened.
62178
62179      if (this.tech_.autoplay()) {
62180        var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
62181        // on browsers which return a promise
62182
62183        if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
62184          playPromise.then(null, function (e) {});
62185        }
62186      }
62187
62188      this.trigger('sourceopen');
62189    }
62190    /**
62191     * handle the sourceended event on the MediaSource
62192     *
62193     * @private
62194     */
62195    ;
62196
62197    _proto.handleSourceEnded_ = function handleSourceEnded_() {
62198      if (!this.inbandTextTracks_.metadataTrack_) {
62199        return;
62200      }
62201
62202      var cues = this.inbandTextTracks_.metadataTrack_.cues;
62203
62204      if (!cues || !cues.length) {
62205        return;
62206      }
62207
62208      var duration = this.duration();
62209      cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
62210    }
62211    /**
62212     * handle the durationchange event on the MediaSource
62213     *
62214     * @private
62215     */
62216    ;
62217
62218    _proto.handleDurationChange_ = function handleDurationChange_() {
62219      this.tech_.trigger('durationchange');
62220    }
62221    /**
62222     * Calls endOfStream on the media source when all active stream types have called
62223     * endOfStream
62224     *
62225     * @param {string} streamType
62226     *        Stream type of the segment loader that called endOfStream
62227     * @private
62228     */
62229    ;
62230
62231    _proto.onEndOfStream = function onEndOfStream() {
62232      var isEndOfStream = this.mainSegmentLoader_.ended_;
62233
62234      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
62235        var mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
62236
62237        if (!mainMediaInfo || mainMediaInfo.hasVideo) {
62238          // if we do not know if the main segment loader contains video yet or if we
62239          // definitively know the main segment loader contains video, then we need to wait
62240          // for both main and audio segment loaders to call endOfStream
62241          isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
62242        } else {
62243          // otherwise just rely on the audio loader
62244          isEndOfStream = this.audioSegmentLoader_.ended_;
62245        }
62246      }
62247
62248      if (!isEndOfStream) {
62249        return;
62250      }
62251
62252      this.stopABRTimer_();
62253      this.sourceUpdater_.endOfStream();
62254    }
62255    /**
62256     * Check if a playlist has stopped being updated
62257     *
62258     * @param {Object} playlist the media playlist object
62259     * @return {boolean} whether the playlist has stopped being updated or not
62260     */
62261    ;
62262
62263    _proto.stuckAtPlaylistEnd_ = function stuckAtPlaylistEnd_(playlist) {
62264      var seekable = this.seekable();
62265
62266      if (!seekable.length) {
62267        // playlist doesn't have enough information to determine whether we are stuck
62268        return false;
62269      }
62270
62271      var expired = this.syncController_.getExpiredTime(playlist, this.duration());
62272
62273      if (expired === null) {
62274        return false;
62275      } // does not use the safe live end to calculate playlist end, since we
62276      // don't want to say we are stuck while there is still content
62277
62278
62279      var absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
62280      var currentTime = this.tech_.currentTime();
62281      var buffered = this.tech_.buffered();
62282
62283      if (!buffered.length) {
62284        // return true if the playhead reached the absolute end of the playlist
62285        return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
62286      }
62287
62288      var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
62289      // end of playlist
62290
62291      return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
62292    }
62293    /**
62294     * Blacklists a playlist when an error occurs for a set amount of time
62295     * making it unavailable for selection by the rendition selection algorithm
62296     * and then forces a new playlist (rendition) selection.
62297     *
62298     * @param {Object=} error an optional error that may include the playlist
62299     * to blacklist
62300     * @param {number=} blacklistDuration an optional number of seconds to blacklist the
62301     * playlist
62302     */
62303    ;
62304
62305    _proto.blacklistCurrentPlaylist = function blacklistCurrentPlaylist(error, blacklistDuration) {
62306      if (error === void 0) {
62307        error = {};
62308      } // If the `error` was generated by the playlist loader, it will contain
62309      // the playlist we were trying to load (but failed) and that should be
62310      // blacklisted instead of the currently selected playlist which is likely
62311      // out-of-date in this scenario
62312
62313
62314      var currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
62315      blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
62316      // trying to load the master OR while we were disposing of the tech
62317
62318      if (!currentPlaylist) {
62319        this.error = error;
62320
62321        if (this.mediaSource.readyState !== 'open') {
62322          this.trigger('error');
62323        } else {
62324          this.sourceUpdater_.endOfStream('network');
62325        }
62326
62327        return;
62328      }
62329
62330      currentPlaylist.playlistErrors_++;
62331      var playlists = this.masterPlaylistLoader_.master.playlists;
62332      var enabledPlaylists = playlists.filter(isEnabled);
62333      var isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === currentPlaylist; // Don't blacklist the only playlist unless it was blacklisted
62334      // forever
62335
62336      if (playlists.length === 1 && blacklistDuration !== Infinity) {
62337        videojs.log.warn("Problem encountered with playlist " + currentPlaylist.id + ". " + 'Trying again since it is the only playlist.');
62338        this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
62339
62340        return this.masterPlaylistLoader_.load(isFinalRendition);
62341      }
62342
62343      if (isFinalRendition) {
62344        // Since we're on the final non-blacklisted playlist, and we're about to blacklist
62345        // it, instead of erring the player or retrying this playlist, clear out the current
62346        // blacklist. This allows other playlists to be attempted in case any have been
62347        // fixed.
62348        var reincluded = false;
62349        playlists.forEach(function (playlist) {
62350          // skip current playlist which is about to be blacklisted
62351          if (playlist === currentPlaylist) {
62352            return;
62353          }
62354
62355          var excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
62356
62357          if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
62358            reincluded = true;
62359            delete playlist.excludeUntil;
62360          }
62361        });
62362
62363        if (reincluded) {
62364          videojs.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
62365          // playlist. This is needed for users relying on the retryplaylist event to catch a
62366          // case where the player might be stuck and looping through "dead" playlists.
62367
62368          this.tech_.trigger('retryplaylist');
62369        }
62370      } // Blacklist this playlist
62371
62372
62373      var excludeUntil;
62374
62375      if (currentPlaylist.playlistErrors_ > this.maxPlaylistRetries) {
62376        excludeUntil = Infinity;
62377      } else {
62378        excludeUntil = Date.now() + blacklistDuration * 1000;
62379      }
62380
62381      currentPlaylist.excludeUntil = excludeUntil;
62382
62383      if (error.reason) {
62384        currentPlaylist.lastExcludeReason_ = error.reason;
62385      }
62386
62387      this.tech_.trigger('blacklistplaylist');
62388      this.tech_.trigger({
62389        type: 'usage',
62390        name: 'vhs-rendition-blacklisted'
62391      });
62392      this.tech_.trigger({
62393        type: 'usage',
62394        name: 'hls-rendition-blacklisted'
62395      }); // TODO: should we select a new playlist if this blacklist wasn't for the currentPlaylist?
62396      // Would be something like media().id !=== currentPlaylist.id and we  would need something
62397      // like `pendingMedia` in playlist loaders to check against that too. This will prevent us
62398      // from loading a new playlist on any blacklist.
62399      // Select a new playlist
62400
62401      var nextPlaylist = this.selectPlaylist();
62402
62403      if (!nextPlaylist) {
62404        this.error = 'Playback cannot continue. No available working or supported playlists.';
62405        this.trigger('error');
62406        return;
62407      }
62408
62409      var logFn = error.internal ? this.logger_ : videojs.log.warn;
62410      var errorMessage = error.message ? ' ' + error.message : '';
62411      logFn((error.internal ? 'Internal problem' : 'Problem') + " encountered with playlist " + currentPlaylist.id + "." + (errorMessage + " Switching to playlist " + nextPlaylist.id + ".")); // if audio group changed reset audio loaders
62412
62413      if (nextPlaylist.attributes.AUDIO !== currentPlaylist.attributes.AUDIO) {
62414        this.delegateLoaders_('audio', ['abort', 'pause']);
62415      } // if subtitle group changed reset subtitle loaders
62416
62417
62418      if (nextPlaylist.attributes.SUBTITLES !== currentPlaylist.attributes.SUBTITLES) {
62419        this.delegateLoaders_('subtitle', ['abort', 'pause']);
62420      }
62421
62422      this.delegateLoaders_('main', ['abort', 'pause']);
62423      var delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
62424      var shouldDelay = typeof nextPlaylist.lastRequest === 'number' && Date.now() - nextPlaylist.lastRequest <= delayDuration; // delay if it's a final rendition or if the last refresh is sooner than half targetDuration
62425
62426      return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
62427    }
62428    /**
62429     * Pause all segment/playlist loaders
62430     */
62431    ;
62432
62433    _proto.pauseLoading = function pauseLoading() {
62434      this.delegateLoaders_('all', ['abort', 'pause']);
62435      this.stopABRTimer_();
62436    }
62437    /**
62438     * Call a set of functions in order on playlist loaders, segment loaders,
62439     * or both types of loaders.
62440     *
62441     * @param {string} filter
62442     *        Filter loaders that should call fnNames using a string. Can be:
62443     *        * all - run on all loaders
62444     *        * audio - run on all audio loaders
62445     *        * subtitle - run on all subtitle loaders
62446     *        * main - run on the main/master loaders
62447     *
62448     * @param {Array|string} fnNames
62449     *        A string or array of function names to call.
62450     */
62451    ;
62452
62453    _proto.delegateLoaders_ = function delegateLoaders_(filter, fnNames) {
62454      var _this7 = this;
62455
62456      var loaders = [];
62457      var dontFilterPlaylist = filter === 'all';
62458
62459      if (dontFilterPlaylist || filter === 'main') {
62460        loaders.push(this.masterPlaylistLoader_);
62461      }
62462
62463      var mediaTypes = [];
62464
62465      if (dontFilterPlaylist || filter === 'audio') {
62466        mediaTypes.push('AUDIO');
62467      }
62468
62469      if (dontFilterPlaylist || filter === 'subtitle') {
62470        mediaTypes.push('CLOSED-CAPTIONS');
62471        mediaTypes.push('SUBTITLES');
62472      }
62473
62474      mediaTypes.forEach(function (mediaType) {
62475        var loader = _this7.mediaTypes_[mediaType] && _this7.mediaTypes_[mediaType].activePlaylistLoader;
62476
62477        if (loader) {
62478          loaders.push(loader);
62479        }
62480      });
62481      ['main', 'audio', 'subtitle'].forEach(function (name) {
62482        var loader = _this7[name + "SegmentLoader_"];
62483
62484        if (loader && (filter === name || filter === 'all')) {
62485          loaders.push(loader);
62486        }
62487      });
62488      loaders.forEach(function (loader) {
62489        return fnNames.forEach(function (fnName) {
62490          if (typeof loader[fnName] === 'function') {
62491            loader[fnName]();
62492          }
62493        });
62494      });
62495    }
62496    /**
62497     * set the current time on all segment loaders
62498     *
62499     * @param {TimeRange} currentTime the current time to set
62500     * @return {TimeRange} the current time
62501     */
62502    ;
62503
62504    _proto.setCurrentTime = function setCurrentTime(currentTime) {
62505      var buffered = findRange(this.tech_.buffered(), currentTime);
62506
62507      if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
62508        // return immediately if the metadata is not ready yet
62509        return 0;
62510      } // it's clearly an edge-case but don't thrown an error if asked to
62511      // seek within an empty playlist
62512
62513
62514      if (!this.masterPlaylistLoader_.media().segments) {
62515        return 0;
62516      } // if the seek location is already buffered, continue buffering as usual
62517
62518
62519      if (buffered && buffered.length) {
62520        return currentTime;
62521      } // cancel outstanding requests so we begin buffering at the new
62522      // location
62523
62524
62525      this.mainSegmentLoader_.resetEverything();
62526      this.mainSegmentLoader_.abort();
62527
62528      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
62529        this.audioSegmentLoader_.resetEverything();
62530        this.audioSegmentLoader_.abort();
62531      }
62532
62533      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
62534        this.subtitleSegmentLoader_.resetEverything();
62535        this.subtitleSegmentLoader_.abort();
62536      } // start segment loader loading in case they are paused
62537
62538
62539      this.load();
62540    }
62541    /**
62542     * get the current duration
62543     *
62544     * @return {TimeRange} the duration
62545     */
62546    ;
62547
62548    _proto.duration = function duration() {
62549      if (!this.masterPlaylistLoader_) {
62550        return 0;
62551      }
62552
62553      var media = this.masterPlaylistLoader_.media();
62554
62555      if (!media) {
62556        // no playlists loaded yet, so can't determine a duration
62557        return 0;
62558      } // Don't rely on the media source for duration in the case of a live playlist since
62559      // setting the native MediaSource's duration to infinity ends up with consequences to
62560      // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
62561      //
62562      // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
62563      // however, few browsers have support for setLiveSeekableRange()
62564      // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
62565      //
62566      // Until a time when the duration of the media source can be set to infinity, and a
62567      // seekable range specified across browsers, just return Infinity.
62568
62569
62570      if (!media.endList) {
62571        return Infinity;
62572      } // Since this is a VOD video, it is safe to rely on the media source's duration (if
62573      // available). If it's not available, fall back to a playlist-calculated estimate.
62574
62575
62576      if (this.mediaSource) {
62577        return this.mediaSource.duration;
62578      }
62579
62580      return Vhs$1.Playlist.duration(media);
62581    }
62582    /**
62583     * check the seekable range
62584     *
62585     * @return {TimeRange} the seekable range
62586     */
62587    ;
62588
62589    _proto.seekable = function seekable() {
62590      return this.seekable_;
62591    };
62592
62593    _proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() {
62594      var audioSeekable;
62595
62596      if (!this.masterPlaylistLoader_) {
62597        return;
62598      }
62599
62600      var media = this.masterPlaylistLoader_.media();
62601
62602      if (!media) {
62603        return;
62604      }
62605
62606      var expired = this.syncController_.getExpiredTime(media, this.duration());
62607
62608      if (expired === null) {
62609        // not enough information to update seekable
62610        return;
62611      }
62612
62613      var master = this.masterPlaylistLoader_.master;
62614      var mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media));
62615
62616      if (mainSeekable.length === 0) {
62617        return;
62618      }
62619
62620      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
62621        media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
62622        expired = this.syncController_.getExpiredTime(media, this.duration());
62623
62624        if (expired === null) {
62625          return;
62626        }
62627
62628        audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media));
62629
62630        if (audioSeekable.length === 0) {
62631          return;
62632        }
62633      }
62634
62635      var oldEnd;
62636      var oldStart;
62637
62638      if (this.seekable_ && this.seekable_.length) {
62639        oldEnd = this.seekable_.end(0);
62640        oldStart = this.seekable_.start(0);
62641      }
62642
62643      if (!audioSeekable) {
62644        // seekable has been calculated based on buffering video data so it
62645        // can be returned directly
62646        this.seekable_ = mainSeekable;
62647      } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
62648        // seekables are pretty far off, rely on main
62649        this.seekable_ = mainSeekable;
62650      } else {
62651        this.seekable_ = videojs.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)]]);
62652      } // seekable is the same as last time
62653
62654
62655      if (this.seekable_ && this.seekable_.length) {
62656        if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
62657          return;
62658        }
62659      }
62660
62661      this.logger_("seekable updated [" + printableRange(this.seekable_) + "]");
62662      this.tech_.trigger('seekablechanged');
62663    }
62664    /**
62665     * Update the player duration
62666     */
62667    ;
62668
62669    _proto.updateDuration = function updateDuration(isLive) {
62670      if (this.updateDuration_) {
62671        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
62672        this.updateDuration_ = null;
62673      }
62674
62675      if (this.mediaSource.readyState !== 'open') {
62676        this.updateDuration_ = this.updateDuration.bind(this, isLive);
62677        this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
62678        return;
62679      }
62680
62681      if (isLive) {
62682        var seekable = this.seekable();
62683
62684        if (!seekable.length) {
62685          return;
62686        } // Even in the case of a live playlist, the native MediaSource's duration should not
62687        // be set to Infinity (even though this would be expected for a live playlist), since
62688        // setting the native MediaSource's duration to infinity ends up with consequences to
62689        // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
62690        //
62691        // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
62692        // however, few browsers have support for setLiveSeekableRange()
62693        // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
62694        //
62695        // Until a time when the duration of the media source can be set to infinity, and a
62696        // seekable range specified across browsers, the duration should be greater than or
62697        // equal to the last possible seekable value.
62698        // MediaSource duration starts as NaN
62699        // It is possible (and probable) that this case will never be reached for many
62700        // sources, since the MediaSource reports duration as the highest value without
62701        // accounting for timestamp offset. For example, if the timestamp offset is -100 and
62702        // we buffered times 0 to 100 with real times of 100 to 200, even though current
62703        // time will be between 0 and 100, the native media source may report the duration
62704        // as 200. However, since we report duration separate from the media source (as
62705        // Infinity), and as long as the native media source duration value is greater than
62706        // our reported seekable range, seeks will work as expected. The large number as
62707        // duration for live is actually a strategy used by some players to work around the
62708        // issue of live seekable ranges cited above.
62709
62710
62711        if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
62712          this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
62713        }
62714
62715        return;
62716      }
62717
62718      var buffered = this.tech_.buffered();
62719      var duration = Vhs$1.Playlist.duration(this.masterPlaylistLoader_.media());
62720
62721      if (buffered.length > 0) {
62722        duration = Math.max(duration, buffered.end(buffered.length - 1));
62723      }
62724
62725      if (this.mediaSource.duration !== duration) {
62726        this.sourceUpdater_.setDuration(duration);
62727      }
62728    }
62729    /**
62730     * dispose of the MasterPlaylistController and everything
62731     * that it controls
62732     */
62733    ;
62734
62735    _proto.dispose = function dispose() {
62736      var _this8 = this;
62737
62738      this.trigger('dispose');
62739      this.decrypter_.terminate();
62740      this.masterPlaylistLoader_.dispose();
62741      this.mainSegmentLoader_.dispose();
62742
62743      if (this.loadOnPlay_) {
62744        this.tech_.off('play', this.loadOnPlay_);
62745      }
62746
62747      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
62748        var groups = _this8.mediaTypes_[type].groups;
62749
62750        for (var id in groups) {
62751          groups[id].forEach(function (group) {
62752            if (group.playlistLoader) {
62753              group.playlistLoader.dispose();
62754            }
62755          });
62756        }
62757      });
62758      this.audioSegmentLoader_.dispose();
62759      this.subtitleSegmentLoader_.dispose();
62760      this.sourceUpdater_.dispose();
62761      this.timelineChangeController_.dispose();
62762      this.stopABRTimer_();
62763
62764      if (this.updateDuration_) {
62765        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
62766      }
62767
62768      this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
62769
62770      this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
62771      this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
62772      this.off();
62773    }
62774    /**
62775     * return the master playlist object if we have one
62776     *
62777     * @return {Object} the master playlist object that we parsed
62778     */
62779    ;
62780
62781    _proto.master = function master() {
62782      return this.masterPlaylistLoader_.master;
62783    }
62784    /**
62785     * return the currently selected playlist
62786     *
62787     * @return {Object} the currently selected playlist object that we parsed
62788     */
62789    ;
62790
62791    _proto.media = function media() {
62792      // playlist loader will not return media if it has not been fully loaded
62793      return this.masterPlaylistLoader_.media() || this.initialMedia_;
62794    };
62795
62796    _proto.areMediaTypesKnown_ = function areMediaTypesKnown_() {
62797      var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62798      var hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
62799      // otherwise check on the segment loader.
62800
62801      var hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
62802
62803      if (!hasMainMediaInfo || !hasAudioMediaInfo) {
62804        return false;
62805      }
62806
62807      return true;
62808    };
62809
62810    _proto.getCodecsOrExclude_ = function getCodecsOrExclude_() {
62811      var _this9 = this;
62812
62813      var media = {
62814        main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
62815        audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
62816      }; // set "main" media equal to video
62817
62818      media.video = media.main;
62819      var playlistCodecs = codecsForPlaylist(this.master(), this.media());
62820      var codecs = {};
62821      var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62822
62823      if (media.main.hasVideo) {
62824        codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC;
62825      }
62826
62827      if (media.main.isMuxed) {
62828        codecs.video += "," + (playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC);
62829      }
62830
62831      if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
62832        codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
62833
62834        media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
62835      } // no codecs, no playback.
62836
62837
62838      if (!codecs.audio && !codecs.video) {
62839        this.blacklistCurrentPlaylist({
62840          playlist: this.media(),
62841          message: 'Could not determine codecs for playlist.',
62842          blacklistDuration: Infinity
62843        });
62844        return;
62845      } // fmp4 relies on browser support, while ts relies on muxer support
62846
62847
62848      var supportFunction = function supportFunction(isFmp4, codec) {
62849        return isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec);
62850      };
62851
62852      var unsupportedCodecs = {};
62853      var unsupportedAudio;
62854      ['video', 'audio'].forEach(function (type) {
62855        if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
62856          var supporter = media[type].isFmp4 ? 'browser' : 'muxer';
62857          unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
62858          unsupportedCodecs[supporter].push(codecs[type]);
62859
62860          if (type === 'audio') {
62861            unsupportedAudio = supporter;
62862          }
62863        }
62864      });
62865
62866      if (usingAudioLoader && unsupportedAudio && this.media().attributes.AUDIO) {
62867        var audioGroup = this.media().attributes.AUDIO;
62868        this.master().playlists.forEach(function (variant) {
62869          var variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
62870
62871          if (variantAudioGroup === audioGroup && variant !== _this9.media()) {
62872            variant.excludeUntil = Infinity;
62873          }
62874        });
62875        this.logger_("excluding audio group " + audioGroup + " as " + unsupportedAudio + " does not support codec(s): \"" + codecs.audio + "\"");
62876      } // if we have any unsupported codecs blacklist this playlist.
62877
62878
62879      if (Object.keys(unsupportedCodecs).length) {
62880        var message = Object.keys(unsupportedCodecs).reduce(function (acc, supporter) {
62881          if (acc) {
62882            acc += ', ';
62883          }
62884
62885          acc += supporter + " does not support codec(s): \"" + unsupportedCodecs[supporter].join(',') + "\"";
62886          return acc;
62887        }, '') + '.';
62888        this.blacklistCurrentPlaylist({
62889          playlist: this.media(),
62890          internal: true,
62891          message: message,
62892          blacklistDuration: Infinity
62893        });
62894        return;
62895      } // check if codec switching is happening
62896
62897
62898      if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
62899        var switchMessages = [];
62900        ['video', 'audio'].forEach(function (type) {
62901          var newCodec = (parseCodecs(_this9.sourceUpdater_.codecs[type] || '')[0] || {}).type;
62902          var oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type;
62903
62904          if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
62905            switchMessages.push("\"" + _this9.sourceUpdater_.codecs[type] + "\" -> \"" + codecs[type] + "\"");
62906          }
62907        });
62908
62909        if (switchMessages.length) {
62910          this.blacklistCurrentPlaylist({
62911            playlist: this.media(),
62912            message: "Codec switching not supported: " + switchMessages.join(', ') + ".",
62913            blacklistDuration: Infinity,
62914            internal: true
62915          });
62916          return;
62917        }
62918      } // TODO: when using the muxer shouldn't we just return
62919      // the codecs that the muxer outputs?
62920
62921
62922      return codecs;
62923    }
62924    /**
62925     * Create source buffers and exlude any incompatible renditions.
62926     *
62927     * @private
62928     */
62929    ;
62930
62931    _proto.tryToCreateSourceBuffers_ = function tryToCreateSourceBuffers_() {
62932      // media source is not ready yet or sourceBuffers are already
62933      // created.
62934      if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
62935        return;
62936      }
62937
62938      if (!this.areMediaTypesKnown_()) {
62939        return;
62940      }
62941
62942      var codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
62943
62944      if (!codecs) {
62945        return;
62946      }
62947
62948      this.sourceUpdater_.createSourceBuffers(codecs);
62949      var codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
62950      this.excludeIncompatibleVariants_(codecString);
62951    }
62952    /**
62953     * Excludes playlists with codecs that are unsupported by the muxer and browser.
62954     */
62955    ;
62956
62957    _proto.excludeUnsupportedVariants_ = function excludeUnsupportedVariants_() {
62958      var _this10 = this;
62959
62960      var playlists = this.master().playlists;
62961      var ids = []; // TODO: why don't we have a property to loop through all
62962      // playlist? Why did we ever mix indexes and keys?
62963
62964      Object.keys(playlists).forEach(function (key) {
62965        var variant = playlists[key]; // check if we already processed this playlist.
62966
62967        if (ids.indexOf(variant.id) !== -1) {
62968          return;
62969        }
62970
62971        ids.push(variant.id);
62972        var codecs = codecsForPlaylist(_this10.master, variant);
62973        var unsupported = [];
62974
62975        if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) {
62976          unsupported.push("audio codec " + codecs.audio);
62977        }
62978
62979        if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video)) {
62980          unsupported.push("video codec " + codecs.video);
62981        }
62982
62983        if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
62984          unsupported.push("text codec " + codecs.text);
62985        }
62986
62987        if (unsupported.length) {
62988          variant.excludeUntil = Infinity;
62989
62990          _this10.logger_("excluding " + variant.id + " for unsupported: " + unsupported.join(', '));
62991        }
62992      });
62993    }
62994    /**
62995     * Blacklist playlists that are known to be codec or
62996     * stream-incompatible with the SourceBuffer configuration. For
62997     * instance, Media Source Extensions would cause the video element to
62998     * stall waiting for video data if you switched from a variant with
62999     * video and audio to an audio-only one.
63000     *
63001     * @param {Object} media a media playlist compatible with the current
63002     * set of SourceBuffers. Variants in the current master playlist that
63003     * do not appear to have compatible codec or stream configurations
63004     * will be excluded from the default playlist selection algorithm
63005     * indefinitely.
63006     * @private
63007     */
63008    ;
63009
63010    _proto.excludeIncompatibleVariants_ = function excludeIncompatibleVariants_(codecString) {
63011      var _this11 = this;
63012
63013      var ids = [];
63014      var playlists = this.master().playlists;
63015      var codecs = unwrapCodecList(parseCodecs(codecString));
63016      var codecCount_ = codecCount(codecs);
63017      var videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null;
63018      var audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null;
63019      Object.keys(playlists).forEach(function (key) {
63020        var variant = playlists[key]; // check if we already processed this playlist.
63021        // or it if it is already excluded forever.
63022
63023        if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
63024          return;
63025        }
63026
63027        ids.push(variant.id);
63028        var blacklistReasons = [];
63028 // get codecs from the playlist for this variant
63029
63030        var variantCodecs = codecsForPlaylist(_this11.masterPlaylistLoader_.master, variant);
63031        var variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
63032        // variant is incompatible. Wait for mux.js to probe
63033
63034        if (!variantCodecs.audio && !variantCodecs.video) {
63035          return;
63036        } // TODO: we can support this by removing the
63037        // old media source and creating a new one, but it will take some work.
63038        // The number of streams cannot change
63039
63040
63041        if (variantCodecCount !== codecCount_) {
63042          blacklistReasons.push("codec count \"" + variantCodecCount + "\" !== \"" + codecCount_ + "\"");
63043        } // only exclude playlists by codec change, if codecs cannot switch
63044        // during playback.
63045
63046
63047        if (!_this11.sourceUpdater_.canChangeType()) {
63048          var variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null;
63049          var variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
63050
63051          if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
63052            blacklistReasons.push("video codec \"" + variantVideoDetails.type + "\" !== \"" + videoDetails.type + "\"");
63053          } // the audio codec cannot change
63054
63055
63056          if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
63057            blacklistReasons.push("audio codec \"" + variantAudioDetails.type + "\" !== \"" + audioDetails.type + "\"");
63058          }
63059        }
63060
63061        if (blacklistReasons.length) {
63062          variant.excludeUntil = Infinity;
63063
63064          _this11.logger_("blacklisting " + variant.id + ": " + blacklistReasons.join(' && '));
63065        }
63066      });
63067    };
63068
63069    _proto.updateAdCues_ = function updateAdCues_(media) {
63070      var offset = 0;
63071      var seekable = this.seekable();
63072
63073      if (seekable.length) {
63074        offset = seekable.start(0);
63075      }
63076
63077      updateAdCues(media, this.cueTagsTrack_, offset);
63078    }
63079    /**
63080     * Calculates the desired forward buffer length based on current time
63081     *
63082     * @return {number} Desired forward buffer length in seconds
63083     */
63084    ;
63085
63086    _proto.goalBufferLength = function goalBufferLength() {
63087      var currentTime = this.tech_.currentTime();
63088      var initial = Config.GOAL_BUFFER_LENGTH;
63089      var rate = Config.GOAL_BUFFER_LENGTH_RATE;
63090      var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
63091      return Math.min(initial + currentTime * rate, max);
63092    }
63093    /**
63094     * Calculates the desired buffer low water line based on current time
63095     *
63096     * @return {number} Desired buffer low water line in seconds
63097     */
63098    ;
63099
63100    _proto.bufferLowWaterLine = function bufferLowWaterLine() {
63101      var currentTime = this.tech_.currentTime();
63102      var initial = Config.BUFFER_LOW_WATER_LINE;
63103      var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
63104      var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
63105      var newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
63106      return Math.min(initial + currentTime * rate, this.experimentalBufferBasedABR ? newMax : max);
63107    };
63108
63109    _proto.bufferHighWaterLine = function bufferHighWaterLine() {
63110      return Config.BUFFER_HIGH_WATER_LINE;
63111    };
63112
63113    return MasterPlaylistController;
63114  }(videojs.EventTarget);
63115  /**
63116   * Returns a function that acts as the Enable/disable playlist function.
63117   *
63118   * @param {PlaylistLoader} loader - The master playlist loader
63119   * @param {string} playlistID - id of the playlist
63120   * @param {Function} changePlaylistFn - A function to be called after a
63121   * playlist's enabled-state has been changed. Will NOT be called if a
63122   * playlist's enabled-state is unchanged
63123   * @param {boolean=} enable - Value to set the playlist enabled-state to
63124   * or if undefined returns the current enabled-state for the playlist
63125   * @return {Function} Function for setting/getting enabled
63126   */
63127
63128
63129  var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) {
63130    return function (enable) {
63131      var playlist = loader.master.playlists[playlistID];
63132      var incompatible = isIncompatible(playlist);
63133      var currentlyEnabled = isEnabled(playlist);
63134
63135      if (typeof enable === 'undefined') {
63136        return currentlyEnabled;
63137      }
63138
63139      if (enable) {
63140        delete playlist.disabled;
63141      } else {
63142        playlist.disabled = true;
63143      }
63144
63145      if (enable !== currentlyEnabled && !incompatible) {
63146        // Ensure the outside world knows about our changes
63147        changePlaylistFn();
63148
63149        if (enable) {
63150          loader.trigger('renditionenabled');
63151        } else {
63152          loader.trigger('renditiondisabled');
63153        }
63154      }
63155
63156      return enable;
63157    };
63158  };
63159  /**
63160   * The representation object encapsulates the publicly visible information
63161   * in a media playlist along with a setter/getter-type function (enabled)
63162   * for changing the enabled-state of a particular playlist entry
63163   *
63164   * @class Representation
63165   */
63166
63167
63168  var Representation = function Representation(vhsHandler, playlist, id) {
63169    var mpc = vhsHandler.masterPlaylistController_,
63170        smoothQualityChange = vhsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
63171
63172    var changeType = smoothQualityChange ? 'smooth' : 'fast';
63173    var qualityChangeFunction = mpc[changeType + "QualityChange_"].bind(mpc); // some playlist attributes are optional
63174
63175    if (playlist.attributes) {
63176      var resolution = playlist.attributes.RESOLUTION;
63177      this.width = resolution && resolution.width;
63178      this.height = resolution && resolution.height;
63179      this.bandwidth = playlist.attributes.BANDWIDTH;
63180    }
63181
63182    this.codecs = codecsForPlaylist(mpc.master(), playlist);
63183    this.playlist = playlist; // The id is simply the ordinality of the media playlist
63184    // within the master playlist
63185
63186    this.id = id; // Partially-apply the enableFunction to create a playlist-
63187    // specific variant
63188
63189    this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
63190  };
63191  /**
63192   * A mixin function that adds the `representations` api to an instance
63193   * of the VhsHandler class
63194   *
63195   * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
63196   * representation API into
63197   */
63198
63199
63200  var renditionSelectionMixin = function renditionSelectionMixin(vhsHandler) {
63201    // Add a single API-specific function to the VhsHandler instance
63202    vhsHandler.representations = function () {
63203      var master = vhsHandler.masterPlaylistController_.master();
63204      var playlists = isAudioOnly(master) ? vhsHandler.masterPlaylistController_.getAudioTrackPlaylists_() : master.playlists;
63205
63206      if (!playlists) {
63207        return [];
63208      }
63209
63210      return playlists.filter(function (media) {
63211        return !isIncompatible(media);
63212      }).map(function (e, i) {
63213        return new Representation(vhsHandler, e, e.id);
63214      });
63215    };
63216  };
63217  /**
63218   * @file playback-watcher.js
63219   *
63220   * Playback starts, and now my watch begins. It shall not end until my death. I shall
63221   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
63222   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
63223   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
63224   * my life and honor to the Playback Watch, for this Player and all the Players to come.
63225   */
63226
63227
63228  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
63229  /**
63230   * Returns whether or not the current time should be considered close to buffered content,
63231   * taking into consideration whether there's enough buffered content for proper playback.
63232   *
63233   * @param {Object} options
63234   *        Options object
63235   * @param {TimeRange} options.buffered
63236   *        Current buffer
63237   * @param {number} options.targetDuration
63238   *        The active playlist's target duration
63239   * @param {number} options.currentTime
63240   *        The current time of the player
63241   * @return {boolean}
63242   *         Whether the current time should be considered close to the buffer
63243   */
63244
63245  var closeToBufferedContent = function closeToBufferedContent(_ref) {
63246    var buffered = _ref.buffered,
63247        targetDuration = _ref.targetDuration,
63248        currentTime = _ref.currentTime;
63249
63250    if (!buffered.length) {
63251      return false;
63252    } // At least two to three segments worth of content should be buffered before there's a
63253    // full enough buffer to consider taking any actions.
63254
63255
63256    if (buffered.end(0) - buffered.start(0) < targetDuration * 2) {
63257      return false;
63258    } // It's possible that, on seek, a remove hasn't completed and the buffered range is
63259    // somewhere past the current time. In that event, don't consider the buffered content
63260    // close.
63261
63262
63263    if (currentTime > buffered.start(0)) {
63264      return false;
63265    } // Since target duration generally represents the max (or close to max) duration of a
63266    // segment, if the buffer is within a segment of the current time, the gap probably
63267    // won't be closed, and current time should be considered close to buffered content.
63268
63269
63270    return buffered.start(0) - currentTime < targetDuration;
63271  };
63272  /**
63273   * @class PlaybackWatcher
63274   */
63275
63276
63277  var PlaybackWatcher = /*#__PURE__*/function () {
63278    /**
63279     * Represents an PlaybackWatcher object.
63280     *
63281     * @class
63282     * @param {Object} options an object that includes the tech and settings
63283     */
63284    function PlaybackWatcher(options) {
63285      var _this = this;
63286
63287      this.masterPlaylistController_ = options.masterPlaylistController;
63288      this.tech_ = options.tech;
63289      this.seekable = options.seekable;
63290      this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
63291      this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
63292      this.media = options.media;
63293      this.consecutiveUpdates = 0;
63294      this.lastRecordedTime = null;
63295      this.timer_ = null;
63296      this.checkCurrentTimeTimeout_ = null;
63297      this.logger_ = logger('PlaybackWatcher');
63298      this.logger_('initialize');
63299
63300      var playHandler = function playHandler() {
63301        return _this.monitorCurrentTime_();
63302      };
63303
63304      var canPlayHandler = function canPlayHandler() {
63305        return _this.monitorCurrentTime_();
63306      };
63307
63308      var waitingHandler = function waitingHandler() {
63309        return _this.techWaiting_();
63310      };
63311
63312      var cancelTimerHandler = function cancelTimerHandler() {
63313        return _this.cancelTimer_();
63314      };
63315
63316      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
63317        return _this.fixesBadSeeks_();
63318      };
63319
63320      var mpc = this.masterPlaylistController_;
63321      var loaderTypes = ['main', 'subtitle', 'audio'];
63322      var loaderChecks = {};
63323      loaderTypes.forEach(function (type) {
63324        loaderChecks[type] = {
63325          reset: function reset() {
63326            return _this.resetSegmentDownloads_(type);
63327          },
63328          updateend: function updateend() {
63329            return _this.checkSegmentDownloads_(type);
63330          }
63331        };
63332        mpc[type + "SegmentLoader_"].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
63333        // isn't changing we want to reset. We cannot assume that the new rendition
63334        // will also be stalled, until after new appends.
63335
63336        mpc[type + "SegmentLoader_"].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
63337        // This prevents one segment playlists (single vtt or single segment content)
63338        // from being detected as stalling. As the buffer will not change in those cases, since
63339        // the buffer is the entire video duration.
63340
63341        _this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
63342      });
63343      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
63344      this.tech_.on('waiting', waitingHandler);
63345      this.tech_.on(timerCancelEvents, cancelTimerHandler);
63346      this.tech_.on('canplay', canPlayHandler);
63347      /*
63348        An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
63349        is surfaced in one of two ways:
63350         1)  The `waiting` event is fired before the player has buffered content, making it impossible
63351            to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
63352            we can check if playback is stalled due to a gap, and skip the gap if necessary.
63353        2)  A source with a gap at the beginning of the stream is loaded programatically while the player
63354            is in a playing state. To catch this case, it's important that our one-time play listener is setup
63355            even if the player is in a playing state
63356      */
63357
63358      this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
63359
63360      this.dispose = function () {
63361        _this.logger_('dispose');
63362
63363        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
63364
63365        _this.tech_.off('waiting', waitingHandler);
63366
63367        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
63368
63369        _this.tech_.off('canplay', canPlayHandler);
63370
63371        _this.tech_.off('play', playHandler);
63372
63373        loaderTypes.forEach(function (type) {
63374          mpc[type + "SegmentLoader_"].off('appendsdone', loaderChecks[type].updateend);
63375          mpc[type + "SegmentLoader_"].off('playlistupdate', loaderChecks[type].reset);
63376
63377          _this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
63378        });
63379
63380        if (_this.checkCurrentTimeTimeout_) {
63381          window_1.clearTimeout(_this.checkCurrentTimeTimeout_);
63382        }
63383
63384        _this.cancelTimer_();
63385      };
63386    }
63387    /**
63388     * Periodically check current time to see if playback stopped
63389     *
63390     * @private
63391     */
63392
63393
63394    var _proto = PlaybackWatcher.prototype;
63395
63396    _proto.monitorCurrentTime_ = function monitorCurrentTime_() {
63397      this.checkCurrentTime_();
63398
63399      if (this.checkCurrentTimeTimeout_) {
63400        window_1.clearTimeout(this.checkCurrentTimeTimeout_);
63401      } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
63402
63403
63404      this.checkCurrentTimeTimeout_ = window_1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
63405    }
63406    /**
63407     * Reset stalled download stats for a specific type of loader
63408     *
63409     * @param {string} type
63410     *        The segment loader type to check.
63411     *
63412     * @listens SegmentLoader#playlistupdate
63413     * @listens Tech#seeking
63414     * @listens Tech#seeked
63415     */
63416    ;
63417
63418    _proto.resetSegmentDownloads_ = function resetSegmentDownloads_(type) {
63419      var loader = this.masterPlaylistController_[type + "SegmentLoader_"];
63420
63421      if (this[type + "StalledDownloads_"] > 0) {
63422        this.logger_("resetting possible stalled download count for " + type + " loader");
63423      }
63424
63425      this[type + "StalledDownloads_"] = 0;
63426      this[type + "Buffered_"] = loader.buffered_();
63427    }
63428    /**
63429     * Checks on every segment `appendsdone` to see
63430     * if segment appends are making progress. If they are not
63431     * and we are still downloading bytes. We blacklist the playlist.
63432     *
63433     * @param {string} type
63434     *        The segment loader type to check.
63435     *
63436     * @listens SegmentLoader#appendsdone
63437     */
63438    ;
63439
63440    _proto.checkSegmentDownloads_ = function checkSegmentDownloads_(type) {
63441      var mpc = this.masterPlaylistController_;
63442      var loader = mpc[type + "SegmentLoader_"];
63443      var buffered = loader.buffered_();
63444      var isBufferedDifferent = isRangeDifferent(this[type + "Buffered_"], buffered);
63445      this[type + "Buffered_"] = buffered; // if another watcher is going to fix the issue or
63446      // the buffered value for this loader changed
63447      // appends are working
63448
63449      if (isBufferedDifferent) {
63450        this.resetSegmentDownloads_(type);
63451        return;
63452      }
63453
63454      this[type + "StalledDownloads_"]++;
63455      this.logger_("found #" + this[type + "StalledDownloads_"] + " " + type + " appends that did not increase buffer (possible stalled download)", {
63456        playlistId: loader.playlist_ && loader.playlist_.id,
63457        buffered: timeRangesToArray(buffered)
63458      }); // after 10 possibly stalled appends with no reset, exclude
63459
63460      if (this[type + "StalledDownloads_"] < 10) {
63461        return;
63462      }
63463
63464      this.logger_(type + " loader stalled download exclusion");
63465      this.resetSegmentDownloads_(type);
63466      this.tech_.trigger({
63467        type: 'usage',
63468        name: "vhs-" + type + "-download-exclusion"
63469      });
63470
63471      if (type === 'subtitle') {
63472        return;
63473      } // TODO: should we exclude audio tracks rather than main tracks
63474      // when type is audio?
63475
63476
63477      mpc.blacklistCurrentPlaylist({
63478        message: "Excessive " + type + " segment downloading detected."
63479      }, Infinity);
63480    }
63481    /**
63482     * The purpose of this function is to emulate the "waiting" event on
63483     * browsers that do not emit it when they are waiting for more
63484     * data to continue playback
63485     *
63486     * @private
63487     */
63488    ;
63489
63490    _proto.checkCurrentTime_ = function checkCurrentTime_() {
63491      if (this.tech_.seeking() && this.fixesBadSeeks_()) {
63492        this.consecutiveUpdates = 0;
63493        this.lastRecordedTime = this.tech_.currentTime();
63494        return;
63495      }
63496
63497      if (this.tech_.paused() || this.tech_.seeking()) {
63498        return;
63499      }
63500
63501      var currentTime = this.tech_.currentTime();
63502      var buffered = this.tech_.buffered();
63503
63504      if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
63505        // If current time is at the end of the final buffered region, then any playback
63506        // stall is most likely caused by buffering in a low bandwidth environment. The tech
63507        // should fire a `waiting` event in this scenario, but due to browser and tech
63508        // inconsistencies. Calling `techWaiting_` here allows us to simulate
63509        // responding to a native `waiting` event when the tech fails to emit one.
63510        return this.techWaiting_();
63511      }
63512
63513      if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
63514        this.consecutiveUpdates++;
63515        this.waiting_();
63516      } else if (currentTime === this.lastRecordedTime) {
63517        this.consecutiveUpdates++;
63518      } else {
63519        this.consecutiveUpdates = 0;
63520        this.lastRecordedTime = currentTime;
63521      }
63522    }
63523    /**
63524     * Cancels any pending timers and resets the 'timeupdate' mechanism
63525     * designed to detect that we are stalled
63526     *
63527     * @private
63528     */
63529    ;
63530
63531    _proto.cancelTimer_ = function cancelTimer_() {
63532      this.consecutiveUpdates = 0;
63533
63534      if (this.timer_) {
63535        this.logger_('cancelTimer_');
63536        clearTimeout(this.timer_);
63537      }
63538
63539      this.timer_ = null;
63540    }
63541    /**
63542     * Fixes situations where there's a bad seek
63543     *
63544     * @return {boolean} whether an action was taken to fix the seek
63545     * @private
63546     */
63547    ;
63548
63549    _proto.fixesBadSeeks_ = function fixesBadSeeks_() {
63550      var seeking = this.tech_.seeking();
63551
63552      if (!seeking) {
63553        return false;
63554      }
63555
63556      var seekable = this.seekable();
63557      var currentTime = this.tech_.currentTime();
63558      var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
63559      var seekTo;
63560
63561      if (isAfterSeekableRange) {
63562        var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
63563
63564        seekTo = seekableEnd;
63565      }
63566
63567      if (this.beforeSeekableWindow_(seekable, currentTime)) {
63568        var seekableStart = seekable.start(0); // sync to the beginning of the live window
63569        // provide a buffer of .1 seconds to handle rounding/imprecise numbers
63570
63571        seekTo = seekableStart + ( // if the playlist is too short and the seekable range is an exact time (can
63572        // happen in live with a 3 segment playlist), then don't use a time delta
63573        seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
63574      }
63575
63576      if (typeof seekTo !== 'undefined') {
63577        this.logger_("Trying to seek outside of seekable at time " + currentTime + " with " + ("seekable range " + printableRange(seekable) + ". Seeking to ") + (seekTo + "."));
63578        this.tech_.setCurrentTime(seekTo);
63579        return true;
63580      }
63581
63582      var buffered = this.tech_.buffered();
63583
63584      if (closeToBufferedContent({
63585        buffered: buffered,
63586        targetDuration: this.media().targetDuration,
63587        currentTime: currentTime
63588      })) {
63589        seekTo = buffered.start(0) + SAFE_TIME_DELTA;
63590        this.logger_("Buffered region starts (" + buffered.start(0) + ") " + (" just beyond seek point (" + currentTime + "). Seeking to " + seekTo + "."));
63591        this.tech_.setCurrentTime(seekTo);
63592        return true;
63593      }
63594
63595      return false;
63596    }
63597    /**
63598     * Handler for situations when we determine the player is waiting.
63599     *
63600     * @private
63601     */
63602    ;
63603
63604    _proto.waiting_ = function waiting_() {
63605      if (this.techWaiting_()) {
63606        return;
63607      }
63607 // All tech waiting checks failed. Use last resort correction
63608
63609
63610      var currentTime = this.tech_.currentTime();
63611      var buffered = this.tech_.buffered();
63612      var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
63613      // region with no indication that anything is amiss (seen in Firefox). Seeking to
63614      // currentTime is usually enough to kickstart the player. This checks that the player
63615      // is currently within a buffered region before attempting a corrective seek.
63616      // Chrome does not appear to continue `timeupdate` events after a `waiting` event
63617      // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
63618      // make sure there is ~3 seconds of forward buffer before taking any corrective action
63619      // to avoid triggering an `unknownwaiting` event when the network is slow.
63620
63621      if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
63622        this.cancelTimer_();
63623        this.tech_.setCurrentTime(currentTime);
63624        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
63625
63626        this.tech_.trigger({
63627          type: 'usage',
63628          name: 'vhs-unknown-waiting'
63629        });
63630        this.tech_.trigger({
63631          type: 'usage',
63632          name: 'hls-unknown-waiting'
63633        });
63634        return;
63635      }
63636    }
63637    /**
63638     * Handler for situations when the tech fires a `waiting` event
63639     *
63640     * @return {boolean}
63641     *         True if an action (or none) was needed to correct the waiting. False if no
63642     *         checks passed
63643     * @private
63644     */
63645    ;
63646
63647    _proto.techWaiting_ = function techWaiting_() {
63648      var seekable = this.seekable();
63649      var currentTime = this.tech_.currentTime();
63650
63651      if (this.tech_.seeking() && this.fixesBadSeeks_()) {
63652        // Tech is seeking or bad seek fixed, no action needed
63653        return true;
63654      }
63655
63656      if (this.tech_.seeking() || this.timer_ !== null) {
63657        // Tech is seeking or already waiting on another action, no action needed
63658        return true;
63659      }
63660
63661      if (this.beforeSeekableWindow_(seekable, currentTime)) {
63662        var livePoint = seekable.end(seekable.length - 1);
63663        this.logger_("Fell out of live window at time " + currentTime + ". Seeking to " + ("live point (seekable end) " + livePoint));
63664        this.cancelTimer_();
63665        this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
63666
63667        this.tech_.trigger({
63668          type: 'usage',
63669          name: 'vhs-live-resync'
63670        });
63671        this.tech_.trigger({
63672          type: 'usage',
63673          name: 'hls-live-resync'
63674        });
63675        return true;
63676      }
63677
63678      var sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_;
63679      var buffered = this.tech_.buffered();
63680      var videoUnderflow = this.videoUnderflow_({
63681        audioBuffered: sourceUpdater.audioBuffered(),
63682        videoBuffered: sourceUpdater.videoBuffered(),
63683        currentTime: currentTime
63684      });
63685
63686      if (videoUnderflow) {
63687        // Even though the video underflowed and was stuck in a gap, the audio overplayed
63688        // the gap, leading currentTime into a buffered range. Seeking to currentTime
63689        // allows the video to catch up to the audio position without losing any audio
63690        // (only suffering ~3 seconds of frozen video and a pause in audio playback).
63691        this.cancelTimer_();
63692        this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
63693
63694        this.tech_.trigger({
63695          type: 'usage',
63696          name: 'vhs-video-underflow'
63697        });
63698        this.tech_.trigger({
63699          type: 'usage',
63700          name: 'hls-video-underflow'
63701        });
63702        return true;
63703      }
63704
63705      var nextRange = findNextRange(buffered, currentTime); // check for gap
63706
63707      if (nextRange.length > 0) {
63708        var difference = nextRange.start(0) - currentTime;
63709        this.logger_("Stopped at " + currentTime + ", setting timer for " + difference + ", seeking " + ("to " + nextRange.start(0)));
63710        this.cancelTimer_();
63711        this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
63712        return true;
63713      } // All checks failed. Returning false to indicate failure to correct waiting
63714
63715
63716      return false;
63717    };
63718
63719    _proto.afterSeekableWindow_ = function afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow) {
63720      if (allowSeeksWithinUnsafeLiveWindow === void 0) {
63721        allowSeeksWithinUnsafeLiveWindow = false;
63722      }
63723
63724      if (!seekable.length) {
63725        // we can't make a solid case if there's no seekable, default to false
63726        return false;
63727      }
63728
63729      var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
63730      var isLive = !playlist.endList;
63731
63732      if (isLive && allowSeeksWithinUnsafeLiveWindow) {
63733        allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
63734      }
63735
63736      if (currentTime > allowedEnd) {
63737        return true;
63738      }
63739
63740      return false;
63741    };
63742
63743    _proto.beforeSeekableWindow_ = function beforeSeekableWindow_(seekable, currentTime) {
63744      if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
63745      seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
63746        return true;
63747      }
63748
63749      return false;
63750    };
63751
63752    _proto.videoUnderflow_ = function videoUnderflow_(_ref2) {
63753      var videoBuffered = _ref2.videoBuffered,
63754          audioBuffered = _ref2.audioBuffered,
63755          currentTime = _ref2.currentTime; // audio only content will not have video underflow :)
63756
63757      if (!videoBuffered) {
63758        return;
63759      }
63760
63761      var gap;
63761 // find a gap in demuxed content.
63762
63763      if (videoBuffered.length && audioBuffered.length) {
63764        // in Chrome audio will continue to play for ~3s when we run out of video
63765        // so we have to check that the video buffer did have some buffer in the
63766        // past.
63767        var lastVideoRange = findRange(videoBuffered, currentTime - 3);
63768        var videoRange = findRange(videoBuffered, currentTime);
63769        var audioRange = findRange(audioBuffered, currentTime);
63770
63771        if (audioRange.length && !videoRange.length && lastVideoRange.length) {
63772          gap = {
63773            start: lastVideoRange.end(0),
63774            end: audioRange.end(0)
63775          };
63776        } // find a gap in muxed content.
63777
63778      } else {
63779        var nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
63780        // stuck in a gap due to video underflow.
63781
63782        if (!nextRange.length) {
63783          gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
63784        }
63785      }
63786
63787      if (gap) {
63788        this.logger_("Encountered a gap in video from " + gap.start + " to " + gap.end + ". " + ("Seeking to current time " + currentTime));
63789        return true;
63790      }
63791
63792      return false;
63793    }
63794    /**
63795     * Timer callback. If playback still has not proceeded, then we seek
63796     * to the start of the next buffered region.
63797     *
63798     * @private
63799     */
63800    ;
63801
63802    _proto.skipTheGap_ = function skipTheGap_(scheduledCurrentTime) {
63803      var buffered = this.tech_.buffered();
63804      var currentTime = this.tech_.currentTime();
63805      var nextRange = findNextRange(buffered, currentTime);
63806      this.cancelTimer_();
63807
63808      if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
63809        return;
63810      }
63811
63812      this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
63813
63814      this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
63815      this.tech_.trigger({
63816        type: 'usage',
63817        name: 'vhs-gap-skip'
63818      });
63819      this.tech_.trigger({
63820        type: 'usage',
63821        name: 'hls-gap-skip'
63822      });
63823    };
63824
63825    _proto.gapFromVideoUnderflow_ = function gapFromVideoUnderflow_(buffered, currentTime) {
63826      // At least in Chrome, if there is a gap in the video buffer, the audio will continue
63827      // playing for ~3 seconds after the video gap starts. This is done to account for
63828      // video buffer underflow/underrun (note that this is not done when there is audio
63829      // buffer underflow/underrun -- in that case the video will stop as soon as it
63830      // encounters the gap, as audio stalls are more noticeable/jarring to a user than
63831      // video stalls). The player's time will reflect the playthrough of audio, so the
63832      // time will appear as if we are in a buffered region, even if we are stuck in a
63833      // "gap."
63834      //
63835      // Example:
63836      // video buffer:   0 => 10.1, 10.2 => 20
63837      // audio buffer:   0 => 20
63838      // overall buffer: 0 => 10.1, 10.2 => 20
63839      // current time: 13
63840      //
63841      // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
63842      // however, the audio continued playing until it reached ~3 seconds past the gap
63843      // (13 seconds), at which point it stops as well. Since current time is past the
63844      // gap, findNextRange will return no ranges.
63845      //
63846      // To check for this issue, we see if there is a gap that starts somewhere within
63847      // a 3 second range (3 seconds +/- 1 second) back from our current time.
63848      var gaps = findGaps(buffered);
63849
63850      for (var i = 0; i < gaps.length; i++) {
63851        var start = gaps.start(i);
63852        var end = gaps.end(i); // gap is starts no more than 4 seconds back
63853
63854        if (currentTime - start < 4 && currentTime - start > 2) {
63855          return {
63856            start: start,
63857            end: end
63858          };
63859        }
63860      }
63861
63862      return null;
63863    };
63864
63865    return PlaybackWatcher;
63866  }();
63867
63868  var defaultOptions = {
63869    errorInterval: 30,
63870    getSource: function getSource(next) {
63871      var tech = this.tech({
63872        IWillNotUseThisInPlugins: true
63873      });
63874      var sourceObj = tech.currentSource_ || this.currentSource();
63875      return next(sourceObj);
63876    }
63877  };
63878  /**
63879   * Main entry point for the plugin
63880   *
63881   * @param {Player} player a reference to a videojs Player instance
63882   * @param {Object} [options] an object with plugin options
63883   * @private
63884   */
63885
63886  var initPlugin = function initPlugin(player, options) {
63887    var lastCalled = 0;
63888    var seekTo = 0;
63889    var localOptions = videojs.mergeOptions(defaultOptions, options);
63890    player.ready(function () {
63891      player.trigger({
63892        type: 'usage',
63893        name: 'vhs-error-reload-initialized'
63894      });
63895      player.trigger({
63896        type: 'usage',
63897        name: 'hls-error-reload-initialized'
63898      });
63899    });
63900    /**
63901     * Player modifications to perform that must wait until `loadedmetadata`
63902     * has been triggered
63903     *
63904     * @private
63905     */
63906
63907    var loadedMetadataHandler = function loadedMetadataHandler() {
63908      if (seekTo) {
63909        player.currentTime(seekTo);
63910      }
63911    };
63912    /**
63913     * Set the source on the player element, play, and seek if necessary
63914     *
63915     * @param {Object} sourceObj An object specifying the source url and mime-type to play
63916     * @private
63917     */
63918
63919
63920    var setSource = function setSource(sourceObj) {
63921      if (sourceObj === null || sourceObj === undefined) {
63922        return;
63923      }
63924
63925      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
63926      player.one('loadedmetadata', loadedMetadataHandler);
63927      player.src(sourceObj);
63928      player.trigger({
63929        type: 'usage',
63930        name: 'vhs-error-reload'
63931      });
63932      player.trigger({
63933        type: 'usage',
63934        name: 'hls-error-reload'
63935      });
63936      player.play();
63937    };
63938    /**
63939     * Attempt to get a source from either the built-in getSource function
63940     * or a custom function provided via the options
63941     *
63942     * @private
63943     */
63944
63945
63946    var errorHandler = function errorHandler() {
63947      // Do not attempt to reload the source if a source-reload occurred before
63948      // 'errorInterval' time has elapsed since the last source-reload
63949      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
63950        player.trigger({
63951          type: 'usage',
63952          name: 'vhs-error-reload-canceled'
63953        });
63954        player.trigger({
63955          type: 'usage',
63956          name: 'hls-error-reload-canceled'
63957        });
63958        return;
63959      }
63960
63961      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
63962        videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
63963        return;
63964      }
63965
63966      lastCalled = Date.now();
63967      return localOptions.getSource.call(player, setSource);
63968    };
63969    /**
63970     * Unbind any event handlers that were bound by the plugin
63971     *
63972     * @private
63973     */
63974
63975
63976    var cleanupEvents = function cleanupEvents() {
63977      player.off('loadedmetadata', loadedMetadataHandler);
63978      player.off('error', errorHandler);
63979      player.off('dispose', cleanupEvents);
63980    };
63981    /**
63982     * Cleanup before re-initializing the plugin
63983     *
63984     * @param {Object} [newOptions] an object with plugin options
63985     * @private
63986     */
63987
63988
63989    var reinitPlugin = function reinitPlugin(newOptions) {
63990      cleanupEvents();
63991      initPlugin(player, newOptions);
63992    };
63993
63994    player.on('error', errorHandler);
63995    player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
63996    // initializing the plugin
63997
63998    player.reloadSourceOnError = reinitPlugin;
63999  };
64000  /**
64001   * Reload the source when an error is detected as long as there
64002   * wasn't an error previously within the last 30 seconds
64003   *
64004   * @param {Object} [options] an object with plugin options
64005   */
64006
64007
64008  var reloadSourceOnError = function reloadSourceOnError(options) {
64009    initPlugin(this, options);
64010  };
64011
64012  var version$4 = "2.10.2";
64013  var version$3 = "5.13.0";
64014  var version$2 = "0.19.0";
64015  var version$1 = "4.7.0";
64016  var version = "3.1.2";
64017  var Vhs = {
64018    PlaylistLoader: PlaylistLoader,
64019    Playlist: Playlist,
64020    utils: utils,
64021    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
64022    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
64023    lastBandwidthSelector: lastBandwidthSelector,
64024    movingAverageBandwidthSelector: movingAverageBandwidthSelector,
64025    comparePlaylistBandwidth: comparePlaylistBandwidth,
64026    comparePlaylistResolution: comparePlaylistResolution,
64027    xhr: xhrFactory()
64028  }; // Define getter/setters for config properties
64029
64030  Object.keys(Config).forEach(function (prop) {
64031    Object.defineProperty(Vhs, prop, {
64032      get: function get() {
64033        videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know 
64033what you are doing");
64034        return Config[prop];
64035      },
64036      set: function set(value) {
64037        videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing");
64038
64039        if (typeof value !== 'number' || value < 0) {
64040          videojs.log.warn("value of Vhs." + prop + " must be greater than or equal to 0");
64041          return;
64042        }
64043
64044        Config[prop] = value;
64045      }
64046    });
64047  });
64048  var LOCAL_STORAGE_KEY = 'videojs-vhs';
64049  /**
64050   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
64051   *
64052   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
64053   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
64054   * @function handleVhsMediaChange
64055   */
64056
64057  var handleVhsMediaChange = function handleVhsMediaChange(qualityLevels, playlistLoader) {
64058    var newPlaylist = playlistLoader.media();
64059    var selectedIndex = -1;
64060
64061    for (var i = 0; i < qualityLevels.length; i++) {
64062      if (qualityLevels[i].id === newPlaylist.id) {
64063        selectedIndex = i;
64064        break;
64065      }
64066    }
64067
64068    qualityLevels.selectedIndex_ = selectedIndex;
64069    qualityLevels.trigger({
64070      selectedIndex: selectedIndex,
64071      type: 'change'
64072    });
64073  };
64074  /**
64075   * Adds quality levels to list once playlist metadata is available
64076   *
64077   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
64078   * @param {Object} vhs Vhs object to listen to for media events.
64079   * @function handleVhsLoadedMetadata
64080   */
64081
64082
64083  var handleVhsLoadedMetadata = function handleVhsLoadedMetadata(qualityLevels, vhs) {
64084    vhs.representations().forEach(function (rep) {
64085      qualityLevels.addQualityLevel(rep);
64086    });
64087    handleVhsMediaChange(qualityLevels, vhs.playlists);
64088  }; // HLS is a source handler, not a tech. Make sure attempts to use it
64089  // as one do not cause exceptions.
64090
64091
64092  Vhs.canPlaySource = function () {
64093    return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
64094  };
64095
64096  var emeKeySystems = function emeKeySystems(keySystemOptions, mainPlaylist, audioPlaylist) {
64097    if (!keySystemOptions) {
64098      return keySystemOptions;
64099    }
64100
64101    var codecs = {};
64102
64103    if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
64104      codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS));
64105    }
64106
64107    if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
64108      codecs.audio = audioPlaylist.attributes.CODECS;
64109    }
64110
64111    var videoContentType = getMimeForCodec(codecs.video);
64112    var audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
64113
64114    var keySystemContentTypes = {};
64115
64116    for (var keySystem in keySystemOptions) {
64117      keySystemContentTypes[keySystem] = {};
64118
64119      if (audioContentType) {
64120        keySystemContentTypes[keySystem].audioContentType = audioContentType;
64121      }
64122
64123      if (videoContentType) {
64124        keySystemContentTypes[keySystem].videoContentType = videoContentType;
64125      } // Default to using the video playlist's PSSH even though they may be different, as
64126      // videojs-contrib-eme will only accept one in the options.
64127      //
64128      // This shouldn't be an issue for most cases as early intialization will handle all
64129      // unique PSSH values, and if they aren't, then encrypted events should have the
64130      // specific information needed for the unique license.
64131
64132
64133      if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
64134        keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
64135      } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
64136      // so we need to prevent overwriting the URL entirely
64137
64138
64139      if (typeof keySystemOptions[keySystem] === 'string') {
64140        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
64141      }
64142    }
64143
64144    return videojs.mergeOptions(keySystemOptions, keySystemContentTypes);
64145  };
64146  /**
64147   * @typedef {Object} KeySystems
64148   *
64149   * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
64150   * Note: not all options are listed here.
64151   *
64152   * @property {Uint8Array} [pssh]
64153   *           Protection System Specific Header
64154   */
64155
64156  /**
64157   * Goes through all the playlists and collects an array of KeySystems options objects
64158   * containing each playlist's keySystems and their pssh values, if available.
64159   *
64160   * @param {Object[]} playlists
64161   *        The playlists to look through
64162   * @param {string[]} keySystems
64163   *        The keySystems to collect pssh values for
64164   *
64165   * @return {KeySystems[]}
64166   *         An array of KeySystems objects containing available key systems and their
64167   *         pssh values
64168   */
64169
64170
64171  var getAllPsshKeySystemsOptions = function getAllPsshKeySystemsOptions(playlists, keySystems) {
64172    return playlists.reduce(function (keySystemsArr, playlist) {
64173      if (!playlist.contentProtection) {
64174        return keySystemsArr;
64175      }
64176
64177      var keySystemsOptions = keySystems.reduce(function (keySystemsObj, keySystem) {
64178        var keySystemOptions = playlist.contentProtection[keySystem];
64179
64180        if (keySystemOptions && keySystemOptions.pssh) {
64181          keySystemsObj[keySystem] = {
64182            pssh: keySystemOptions.pssh
64183          };
64184        }
64185
64186        return keySystemsObj;
64187      }, {});
64188
64189      if (Object.keys(keySystemsOptions).length) {
64190        keySystemsArr.push(keySystemsOptions);
64191      }
64192
64193      return keySystemsArr;
64194    }, []);
64195  };
64196  /**
64197   * Returns a promise that waits for the
64198   * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
64199   *
64200   * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
64201   * browsers.
64202   *
64203   * As per the above ticket, this is particularly important for Chrome, where, if
64204   * unencrypted content is appended before encrypted content and the key session has not
64205   * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
64206   * during playback.
64207   *
64208   * @param {Object} player
64209   *        The player instance
64210   * @param {Object[]} sourceKeySystems
64211   *        The key systems options from the player source
64212   * @param {Object} [audioMedia]
64213   *        The active audio media playlist (optional)
64214   * @param {Object[]} mainPlaylists
64215   *        The playlists found on the master playlist object
64216   *
64217   * @return {Object}
64218   *         Promise that resolves when the key session has been created
64219   */
64220
64221
64222  var waitForKeySessionCreation = function waitForKeySessionCreation(_ref) {
64223    var player = _ref.player,
64224        sourceKeySystems = _ref.sourceKeySystems,
64225        audioMedia = _ref.audioMedia,
64226        mainPlaylists = _ref.mainPlaylists;
64227
64228    if (!player.eme.initializeMediaKeys) {
64229      return Promise.resolve();
64230    } // TODO should all audio PSSH values be initialized for DRM?
64231    //
64232    // All unique video rendition pssh values are initialized for DRM, but here only
64233    // the initial audio playlist license is initialized. In theory, an encrypted
64234    // event should be fired if the user switches to an alternative audio playlist
64235    // where a license is required, but this case hasn't yet been tested. In addition, there
64236    // may be many alternate audio playlists unlikely to be used (e.g., multiple different
64237    // languages).
64238
64239
64240    var playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
64241    var keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
64242    var initializationFinishedPromises = [];
64243    var keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
64244    // only place where it should not be deduped is for ms-prefixed APIs, but the early
64245    // return for IE11 above, and the existence of modern EME APIs in addition to
64246    // ms-prefixed APIs on Edge should prevent this from being a concern.
64247    // initializeMediaKeys also won't use the webkit-prefixed APIs.
64248
64249    keySystemsOptionsArr.forEach(function (keySystemsOptions) {
64250      keySessionCreatedPromises.push(new Promise(function (resolve, reject) {
64251        player.tech_.one('keysessioncreated', resolve);
64252      }));
64253      initializationFinishedPromises.push(new Promise(function (resolve, reject) {
64254        player.eme.initializeMediaKeys({
64255          keySystems: keySystemsOptions
64256        }, function (err) {
64257          if (err) {
64258            reject(err);
64259            return;
64260          }
64261
64262          resolve();
64263        });
64264      }));
64265    }); // The reasons Promise.race is chosen over Promise.any:
64266    //
64267    // * Promise.any is only available in Safari 14+.
64268    // * None of these promises are expected to reject. If they do reject, it might be
64269    //   better here for the race to surface the rejection, rather than mask it by using
64270    //   Promise.any.
64271
64272    return Promise.race([// If a session was previously created, these will all finish resolving without
64273    // creating a new session, otherwise it will take until the end of all license
64274    // requests, which is why the key session check is used (to make setup much faster).
64275    Promise.all(initializationFinishedPromises), // Once a single session is created, the browser knows DRM will be used.
64276    Promise.race(keySessionCreatedPromises)]);
64277  };
64278  /**
64279   * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
64280   * there are keySystems on the source, sets up source options to prepare the source for
64281   * eme.
64282   *
64283   * @param {Object} player
64284   *        The player instance
64285   * @param {Object[]} sourceKeySystems
64286   *        The key systems options from the player source
64287   * @param {Object} media
64288   *        The active media playlist
64289   * @param {Object} [audioMedia]
64290   *        The active audio media playlist (optional)
64291   *
64292   * @return {boolean}
64293   *         Whether or not options were configured and EME is available
64294   */
64295
64296
64297  var setupEmeOptions = function setupEmeOptions(_ref2) {
64298    var player = _ref2.player,
64299        sourceKeySystems = _ref2.sourceKeySystems,
64300        media = _ref2.media,
64301        audioMedia = _ref2.audioMedia;
64302    var sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
64303
64304    if (!sourceOptions) {
64305      return false;
64306    }
64307
64308    player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
64309    // do nothing.
64310
64311    if (sourceOptions && !player.eme) {
64312      videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
64313      return false;
64314    }
64315
64316    return true;
64317  };
64318
64319  var getVhsLocalStorage = function getVhsLocalStorage() {
64320    if (!window_1.localStorage) {
64321      return null;
64322    }
64323
64324    var storedObject = window_1.localStorage.getItem(LOCAL_STORAGE_KEY);
64325
64326    if (!storedObject) {
64327      return null;
64328    }
64329
64330    try {
64331      return JSON.parse(storedObject);
64332    } catch (e) {
64333      // someone may have tampered with the value
64334      return null;
64335    }
64336  };
64337
64338  var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
64339    if (!window_1.localStorage) {
64340      return false;
64341    }
64342
64343    var objectToStore = getVhsLocalStorage();
64344    objectToStore = objectToStore ? videojs.mergeOptions(objectToStore, options) : options;
64345
64346    try {
64347      window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
64348    } catch (e) {
64349      // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
64350      // storage is set to 0).
64351      // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
64352      // No need to perform any operation.
64353      return false;
64354    }
64355
64356    return objectToStore;
64357  };
64358  /**
64359   * Parses VHS-supported media types from data URIs. See
64360   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
64361   * for information on data URIs.
64362   *
64363   * @param {string} dataUri
64364   *        The data URI
64365   *
64366   * @return {string|Object}
64367   *         The parsed object/string, or the original string if no supported media type
64368   *         was found
64369   */
64370
64371
64372  var expandDataUri = function expandDataUri(dataUri) {
64373    if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
64374      return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
64375    } // no known case for this data URI, return the string as-is
64376
64377
64378    return dataUri;
64379  };
64380  /**
64381   * Whether the browser has built-in HLS support.
64382   */
64383
64384
64385  Vhs.supportsNativeHls = function () {
64386    if (!document_1 || !document_1.createElement) {
64387      return false;
64388    }
64389
64390    var video = document_1.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
64391
64392    if (!videojs.getTech('Html5').isSupported()) {
64393      return false;
64394    } // HLS manifests can go by many mime-types
64395
64396
64397    var canPlay = [// Apple santioned
64398    'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
64399    'audio/mpegurl', // Very common
64400    'audio/x-mpegurl', // Very common
64401    'application/x-mpegurl', // Included for completeness
64402    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
64403    return canPlay.some(function (canItPlay) {
64404      return /maybe|probably/i.test(video.canPlayType(canItPlay));
64405    });
64406  }();
64407
64408  Vhs.supportsNativeDash = function () {
64409    if (!document_1 || !document_1.createElement || !videojs.getTech('Html5').isSupported()) {
64410      return false;
64411    }
64412
64413    return /maybe|probably/i.test(document_1.createElement('video').canPlayType('application/dash+xml'));
64414  }();
64415
64416  Vhs.supportsTypeNatively = function (type) {
64417    if (type === 'hls') {
64418      return Vhs.supportsNativeHls;
64419    }
64420
64421    if (type === 'dash') {
64422      return Vhs.supportsNativeDash;
64423    }
64424
64425    return false;
64426  };
64427  /**
64428   * HLS is a source handler, not a tech. Make sure attempts to use it
64429   * as one do not cause exceptions.
64430   */
64431
64432
64433  Vhs.isSupported = function () {
64434    return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
64435  };
64436
64437  var Component = videojs.getComponent('Component');
64438  /**
64439   * The Vhs Handler object, where we orchestrate all of the parts
64440   * of HLS to interact with video.js
64441   *
64442   * @class VhsHandler
64443   * @extends videojs.Component
64444   * @param {Object} source the soruce object
64445   * @param {Tech} tech the parent tech object
64446   * @param {Object} options optional and required options
64447   */
64448
64449  var VhsHandler = /*#__PURE__*/function (_Component) {
64450    inheritsLoose(VhsHandler, _Component);
64451
64452    function VhsHandler(source, tech, options) {
64453      var _this;
64454
64455      _this = _Component.call(this, tech, videojs.mergeOptions(options.hls, options.vhs)) || this;
64456
64457      if (options.hls && Object.keys(options.hls).length) {
64458        videojs.log.warn('Using hls options is deprecated. Use vhs instead.');
64459      } // if a tech level `initialBandwidth` option was passed
64460      // use that over the VHS level `bandwidth` option
64461
64462
64463      if (typeof options.initialBandwidth === 'number') {
64464        _this.options_.bandwidth = options.initialBandwidth;
64465      }
64466
64467      _this.logger_ = logger('VhsHandler'); // tech.player() is deprecated but setup a reference to HLS for
64468      // backwards-compatibility
64469
64470      if (tech.options_ && tech.options_.playerId) {
64471        var _player = videojs(tech.options_.playerId);
64472
64473        if (!_player.hasOwnProperty('hls')) {
64474          Object.defineProperty(_player, 'hls', {
64475            get: function get() {
64476              videojs.log.warn('player.hls is deprecated. Use player.tech().vhs instead.');
64477              tech.trigger({
64478                type: 'usage',
64479                name: 'hls-player-access'
64480              });
64481              return assertThisInitialized(_this);
64482            },
64483            configurable: true
64484          });
64485        }
64486
64487        if (!_player.hasOwnProperty('vhs')) {
64488          Object.defineProperty(_player, 'vhs', {
64489            get: function get() {
64490              videojs.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.');
64491              tech.trigger({
64492                type: 'usage',
64493                name: 'vhs-player-access'
64494              });
64495              return assertThisInitialized(_this);
64496            },
64497            configurable: true
64498          });
64499        }
64500
64501        if (!_player.hasOwnProperty('dash')) {
64502          Object.defineProperty(_player, 'dash', {
64503            get: function get() {
64504              videojs.log.warn('player.dash is deprecated. Use player.tech().vhs instead.');
64505              return assertThisInitialized(_this);
64506            },
64507            configurable: true
64508          });
64509        }
64510
64511        _this.player_ = _player;
64512      }
64513
64514      _this.tech_ = tech;
64515      _this.source_ = source;
64516      _this.stats = {};
64517      _this.ignoreNextSeekingEvent_ = false;
64518
64519      _this.setOptions_();
64520
64521      if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
64522        tech.overrideNativeAudioTracks(true);
64523        tech.overrideNativeVideoTracks(true);
64524      } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
64525        // overriding native HLS only works if audio tracks have been emulated
64526        // error early if we're misconfigured
64527        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
64528      } // listen for fullscreenchange events for this player so that we
64529      // can adjust our quality selection quickly
64530
64531
64532      _this.on(document_1, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
64533        var fullscreenElement = document_1.fullscreenElement || document_1.webkitFullscreenElement || document_1.mozFullScreenElement || document_1.msFullscreenElement;
64534
64535        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
64536          _this.masterPlaylistController_.fastQualityChange_();
64537        } else {
64538          // When leaving fullscreen, since the in page pixel dimensions should be smaller
64539          // than full screen, see if there should be a rendition switch down to preserve
64540          // bandwidth.
64541          _this.masterPlaylistController_.checkABR_();
64542        }
64543      });
64544
64545      _this.on(_this.tech_, 'seeking', function () {
64546        if (this.ignoreNextSeekingEvent_) {
64547          this.ignoreNextSeekingEvent_ = false;
64548          return;
64549        }
64550
64551        this.setCurrentTime(this.tech_.currentTime());
64552      });
64553
64554      _this.on(_this.tech_, 'error', function () {
64555        // verify that the error was real and we are loaded
64556        // enough to have mpc loaded.
64557        if (this.tech_.error() && this.masterPlaylistController_) {
64558          this.masterPlaylistController_.pauseLoading();
64559        }
64560      });
64561
64562      _this.on(_this.tech_, 'play', _this.play);
64563
64564      return _this;
64565    }
64566
64567    var _proto = VhsHandler.prototype;
64568
64569    _proto.setOptions_ = function setOptions_() {
64570      var _this2 = this; // defaults
64571
64572
64573      this.options_.withCredentials = this.options_.withCredentials || false;
64574      this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true;
64575      this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
64576      this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
64577      this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
64578      this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
64579      this.options_.customTagParsers = this.options_.customTagParsers || [];
64580      this.options_.customTagMappers = this.options_.customTagMappers || [];
64581      this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
64582
64583      if (typeof this.options_.blacklistDuration !== 'number') {
64584        this.options_.blacklistDuration = 5 * 60;
64585      }
64586
64587      if (typeof this.options_.bandwidth !== 'number') {
64588        if (this.options_.useBandwidthFromLocalStorage) {
64589          var storedObject = getVhsLocalStorage();
64590
64591          if (storedObject && storedObject.bandwidth) {
64592            this.options_.bandwidth = storedObject.bandwidth;
64593            this.tech_.trigger({
64594              type: 'usage',
64595              name: 'vhs-bandwidth-from-local-storage'
64596            });
64597            this.tech_.trigger({
64598              type: 'usage',
64599              name: 'hls-bandwidth-from-local-storage'
64600            });
64601          }
64602
64603          if (storedObject && storedObject.throughput) {
64604            this.options_.throughput = storedObject.throughput;
64605            this.tech_.trigger({
64606              type: 'usage',
64607              name: 'vhs-throughput-from-local-storage'
64608            });
64609            this.tech_.trigger({
64610              type: 'usage',
64611              name: 'hls-throughput-from-local-storage'
64612            });
64613          }
64614        }
64615      } // if bandwidth was not set by options or pulled from local storage, start playlist
64616      // selection at a reasonable bandwidth
64617
64618
64619      if (typeof this.options_.bandwidth !== 'number') {
64620        this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
64621      } // If the bandwidth number is unchanged from the initial setting
64622      // then this takes precedence over the enableLowInitialPlaylist option
64623
64624
64625      this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
64626
64627      ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'experimentalBufferBasedABR', 'liveRangeSafeTimeDelta', 'experimentalLLHLS', 'experimentalExactManifestTimings', 'experimentalLeastPixelDiffSelector'].forEach(function (option) {
64628        if (typeof _this2.source_[option] !== 'undefined') {
64629          _this2.options_[option] = _this2.source_[option];
64630        }
64631      });
64632      this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
64633      this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
64634    }
64635    /**
64636     * called when player.src gets called, handle a new source
64637     *
64638     * @param {Object} src the source object to handle
64639     */
64640    ;
64641
64642    _proto.src = function src(_src, type) {
64643      var _this3 = this; // do nothing if the src is falsey
64644
64645
64646      if (!_src) {
64647        return;
64648      }
64649
64650      this.setOptions_(); // add master playlist controller options
64651
64652      this.options_.src = expandDataUri(this.source_.src);
64653      this.options_.tech = this.tech_;
64654      this.options_.externVhs = Vhs;
64655      this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
64656
64657      this.options_.seekTo = function (time) {
64658        _this3.tech_.setCurrentTime(time);
64659      };
64660
64661      if (this.options_.smoothQualityChange) {
64662        videojs.log.warn('smoothQualityChange is deprecated and will be removed in the next major version');
64663      }
64664
64665      this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
64666      var playbackWatcherOptions = videojs.mergeOptions({
64667        liveRangeSafeTimeDelta: SAFE_TIME_DELTA
64668      }, this.options_, {
64669        seekable: function seekable() {
64670          return _this3.seekable();
64671        },
64672        media: function media() {
64673          return _this3.masterPlaylistController_.media();
64674        },
64675        masterPlaylistController: this.masterPlaylistController_
64676      });
64677      this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
64678      this.masterPlaylistController_.on('error', function () {
64679        var player = videojs.players[_this3.tech_.options_.playerId];
64680        var error = _this3.masterPlaylistController_.error;
64681
64682        if (typeof error === 'object' && !error.code) {
64683          error.code = 3;
64684        } else if (typeof error === 'string') {
64685          error = {
64686            message: error,
64687            code: 3
64688          };
64689        }
64690
64691        player.error(error);
64692      });
64693      var defaultSelector = this.options_.experimentalBufferBasedABR ? Vhs.mov
64693ingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
64694      // compatibility with < v2
64695
64696      this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
64697      this.masterPlaylistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
64698
64699      this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
64700      this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
64701      // controller. Using a custom property for backwards compatibility
64702      // with < v2
64703
64704      Object.defineProperties(this, {
64705        selectPlaylist: {
64706          get: function get() {
64707            return this.masterPlaylistController_.selectPlaylist;
64708          },
64709          set: function set(selectPlaylist) {
64710            this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
64711          }
64712        },
64713        throughput: {
64714          get: function get() {
64715            return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
64716          },
64717          set: function set(throughput) {
64718            this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
64719            // for the cumulative average
64720
64721            this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
64722          }
64723        },
64724        bandwidth: {
64725          get: function get() {
64726            return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
64727          },
64728          set: function set(bandwidth) {
64729            this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
64730            // `count` is set to zero that current value of `rate` isn't included
64731            // in the cumulative average
64732
64733            this.masterPlaylistController_.mainSegmentLoader_.throughput = {
64734              rate: 0,
64735              count: 0
64736            };
64737          }
64738        },
64739
64740        /**
64741         * `systemBandwidth` is a combination of two serial processes bit-rates. The first
64742         * is the network bitrate provided by `bandwidth` and the second is the bitrate of
64743         * the entire process after that - decryption, transmuxing, and appending - provided
64744         * by `throughput`.
64745         *
64746         * Since the two process are serial, the overall system bandwidth is given by:
64747         *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
64748         */
64749        systemBandwidth: {
64750          get: function get() {
64751            var invBandwidth = 1 / (this.bandwidth || 1);
64752            var invThroughput;
64753
64754            if (this.throughput > 0) {
64755              invThroughput = 1 / this.throughput;
64756            } else {
64757              invThroughput = 0;
64758            }
64759
64760            var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
64761            return systemBitrate;
64762          },
64763          set: function set() {
64764            videojs.log.error('The "systemBandwidth" property is read-only');
64765          }
64766        }
64767      });
64768
64769      if (this.options_.bandwidth) {
64770        this.bandwidth = this.options_.bandwidth;
64771      }
64772
64773      if (this.options_.throughput) {
64774        this.throughput = this.options_.throughput;
64775      }
64776
64777      Object.defineProperties(this.stats, {
64778        bandwidth: {
64779          get: function get() {
64780            return _this3.bandwidth || 0;
64781          },
64782          enumerable: true
64783        },
64784        mediaRequests: {
64785          get: function get() {
64786            return _this3.masterPlaylistController_.mediaRequests_() || 0;
64787          },
64788          enumerable: true
64789        },
64790        mediaRequestsAborted: {
64791          get: function get() {
64792            return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
64793          },
64794          enumerable: true
64795        },
64796        mediaRequestsTimedout: {
64797          get: function get() {
64798            return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
64799          },
64800          enumerable: true
64801        },
64802        mediaRequestsErrored: {
64803          get: function get() {
64804            return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
64805          },
64806          enumerable: true
64807        },
64808        mediaTransferDuration: {
64809          get: function get() {
64810            return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
64811          },
64812          enumerable: true
64813        },
64814        mediaBytesTransferred: {
64815          get: function get() {
64816            return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
64817          },
64818          enumerable: true
64819        },
64820        mediaSecondsLoaded: {
64821          get: function get() {
64822            return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
64823          },
64824          enumerable: true
64825        },
64826        mediaAppends: {
64827          get: function get() {
64828            return _this3.masterPlaylistController_.mediaAppends_() || 0;
64829          },
64830          enumerable: true
64831        },
64832        mainAppendsToLoadedData: {
64833          get: function get() {
64834            return _this3.masterPlaylistController_.mainAppendsToLoadedData_() || 0;
64835          },
64836          enumerable: true
64837        },
64838        audioAppendsToLoadedData: {
64839          get: function get() {
64840            return _this3.masterPlaylistController_.audioAppendsToLoadedData_() || 0;
64841          },
64842          enumerable: true
64843        },
64844        appendsToLoadedData: {
64845          get: function get() {
64846            return _this3.masterPlaylistController_.appendsToLoadedData_() || 0;
64847          },
64848          enumerable: true
64849        },
64850        timeToLoadedData: {
64851          get: function get() {
64852            return _this3.masterPlaylistController_.timeToLoadedData_() || 0;
64853          },
64854          enumerable: true
64855        },
64856        buffered: {
64857          get: function get() {
64858            return timeRangesToArray(_this3.tech_.buffered());
64859          },
64860          enumerable: true
64861        },
64862        currentTime: {
64863          get: function get() {
64864            return _this3.tech_.currentTime();
64865          },
64866          enumerable: true
64867        },
64868        currentSource: {
64869          get: function get() {
64870            return _this3.tech_.currentSource_;
64871          },
64872          enumerable: true
64873        },
64874        currentTech: {
64875          get: function get() {
64876            return _this3.tech_.name_;
64877          },
64878          enumerable: true
64879        },
64880        duration: {
64881          get: function get() {
64882            return _this3.tech_.duration();
64883          },
64884          enumerable: true
64885        },
64886        master: {
64887          get: function get() {
64888            return _this3.playlists.master;
64889          },
64890          enumerable: true
64891        },
64892        playerDimensions: {
64893          get: function get() {
64894            return _this3.tech_.currentDimensions();
64895          },
64896          enumerable: true
64897        },
64898        seekable: {
64899          get: function get() {
64900            return timeRangesToArray(_this3.tech_.seekable());
64901          },
64902          enumerable: true
64903        },
64904        timestamp: {
64905          get: function get() {
64906            return Date.now();
64907          },
64908          enumerable: true
64909        },
64910        videoPlaybackQuality: {
64911          get: function get() {
64912            return _this3.tech_.getVideoPlaybackQuality();
64913          },
64914          enumerable: true
64915        }
64916      });
64917      this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
64918      this.tech_.on('bandwidthupdate', function () {
64919        if (_this3.options_.useBandwidthFromLocalStorage) {
64920          updateVhsLocalStorage({
64921            bandwidth: _this3.bandwidth,
64922            throughput: Math.round(_this3.throughput)
64923          });
64924        }
64925      });
64926      this.masterPlaylistController_.on('selectedinitialmedia', function () {
64927        // Add the manual rendition mix-in to VhsHandler
64928        renditionSelectionMixin(_this3);
64929      });
64930      this.masterPlaylistController_.sourceUpdater_.on('createdsourcebuffers', function () {
64931        _this3.setupEme_();
64932      }); // the bandwidth of the primary segment loader is our best
64933      // estimate of overall bandwidth
64934
64935      this.on(this.masterPlaylistController_, 'progress', function () {
64936        this.tech_.trigger('progress');
64937      }); // In the live case, we need to ignore the very first `seeking` event since
64938      // that will be the result of the seek-to-live behavior
64939
64940      this.on(this.masterPlaylistController_, 'firstplay', function () {
64941        this.ignoreNextSeekingEvent_ = true;
64942      });
64943      this.setupQualityLevels_(); // do nothing if the tech has been disposed already
64944      // this can occur if someone sets the src in player.ready(), for instance
64945
64946      if (!this.tech_.el()) {
64947        return;
64948      }
64949
64950      this.mediaSourceUrl_ = window_1.URL.createObjectURL(this.masterPlaylistController_.mediaSource);
64951      this.tech_.src(this.mediaSourceUrl_);
64952    }
64953    /**
64954     * If necessary and EME is available, sets up EME options and waits for key session
64955     * creation.
64956     *
64957     * This function also updates the source updater so taht it can be used, as for some
64958     * browsers, EME must be configured before content is appended (if appending unencrypted
64959     * content before encrypted content).
64960     */
64961    ;
64962
64963    _proto.setupEme_ = function setupEme_() {
64964      var _this4 = this;
64965
64966      var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
64967      var didSetupEmeOptions = setupEmeOptions({
64968        player: this.player_,
64969        sourceKeySystems: this.source_.keySystems,
64970        media: this.playlists.media(),
64971        audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
64972      });
64973      this.player_.tech_.on('keystatuschange', function (e) {
64974        if (e.status === 'output-restricted') {
64975          _this4.masterPlaylistController_.blacklistCurrentPlaylist({
64976            playlist: _this4.masterPlaylistController_.media(),
64977            message: "DRM keystatus changed to " + e.status + ". Playlist will fail to play. Check for HDCP content.",
64978            blacklistDuration: Infinity
64979          });
64980        }
64981      }); // In IE11 this is too early to initialize media keys, and IE11 does not support
64982      // promises.
64983
64984      if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) {
64985        // If EME options were not set up, we've done all we could to initialize EME.
64986        this.masterPlaylistController_.sourceUpdater_.initializedEme();
64987        return;
64988      }
64989
64990      this.logger_('waiting for EME key session creation');
64991      waitForKeySessionCreation({
64992        player: this.player_,
64993        sourceKeySystems: this.source_.keySystems,
64994        audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
64995        mainPlaylists: this.playlists.master.playlists
64996      }).then(function () {
64997        _this4.logger_('created EME key session');
64998
64999        _this4.masterPlaylistController_.sourceUpdater_.initializedEme();
65000      })["catch"](function (err) {
65001        _this4.logger_('error while creating EME key session', err);
65002
65003        _this4.player_.error({
65004          message: 'Failed to initialize media keys for EME',
65005          code: 3
65006        });
65007      });
65008    }
65009    /**
65010     * Initializes the quality levels and sets listeners to update them.
65011     *
65012     * @method setupQualityLevels_
65013     * @private
65014     */
65015    ;
65016
65017    _proto.setupQualityLevels_ = function setupQualityLevels_() {
65018      var _this5 = this;
65019
65020      var player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
65021      // or qualityLevels_ listeners have already been setup, do nothing.
65022
65023      if (!player || !player.qualityLevels || this.qualityLevels_) {
65024        return;
65025      }
65026
65027      this.qualityLevels_ = player.qualityLevels();
65028      this.masterPlaylistController_.on('selectedinitialmedia', function () {
65029        handleVhsLoadedMetadata(_this5.qualityLevels_, _this5);
65030      });
65031      this.playlists.on('mediachange', function () {
65032        handleVhsMediaChange(_this5.qualityLevels_, _this5.playlists);
65033      });
65034    }
65035    /**
65036     * return the version
65037     */
65038    ;
65039
65040    VhsHandler.version = function version$5() {
65041      return {
65042        '@videojs/http-streaming': version$4,
65043        'mux.js': version$3,
65044        'mpd-parser': version$2,
65045        'm3u8-parser': version$1,
65046        'aes-decrypter': version
65047      };
65048    }
65049    /**
65050     * return the version
65051     */
65052    ;
65053
65054    _proto.version = function version() {
65055      return this.constructor.version();
65056    };
65057
65058    _proto.canChangeType = function canChangeType() {
65059      return SourceUpdater.canChangeType();
65060    }
65061    /**
65062     * Begin playing the video.
65063     */
65064    ;
65065
65066    _proto.play = function play() {
65067      this.masterPlaylistController_.play();
65068    }
65069    /**
65070     * a wrapper around the function in MasterPlaylistController
65071     */
65072    ;
65073
65074    _proto.setCurrentTime = function setCurrentTime(currentTime) {
65075      this.masterPlaylistController_.setCurrentTime(currentTime);
65076    }
65077    /**
65078     * a wrapper around the function in MasterPlaylistController
65079     */
65080    ;
65081
65082    _proto.duration = function duration() {
65083      return this.masterPlaylistController_.duration();
65084    }
65085    /**
65086     * a wrapper around the function in MasterPlaylistController
65087     */
65088    ;
65089
65090    _proto.seekable = function seekable() {
65091      return this.masterPlaylistController_.seekable();
65092    }
65093    /**
65094     * Abort all outstanding work and cleanup.
65095     */
65096    ;
65097
65098    _proto.dispose = function dispose() {
65099      if (this.playbackWatcher_) {
65100        this.playbackWatcher_.dispose();
65101      }
65102
65103      if (this.masterPlaylistController_) {
65104        this.masterPlaylistController_.dispose();
65105      }
65106
65107      if (this.qualityLevels_) {
65108        this.qualityLevels_.dispose();
65109      }
65110
65111      if (this.player_) {
65112        delete this.player_.vhs;
65113        delete this.player_.dash;
65114        delete this.player_.hls;
65115      }
65116
65117      if (this.tech_ && this.tech_.vhs) {
65118        delete this.tech_.vhs;
65119      } // don't check this.tech_.hls as it will log a deprecated warning
65120
65121
65122      if (this.tech_) {
65123        delete this.tech_.hls;
65124      }
65125
65126      if (this.mediaSourceUrl_ && window_1.URL.revokeObjectURL) {
65127        window_1.URL.revokeObjectURL(this.mediaSourceUrl_);
65128        this.mediaSourceUrl_ = null;
65129      }
65130
65131      _Component.prototype.dispose.call(this);
65132    };
65133
65134    _proto.convertToProgramTime = function convertToProgramTime(time, callback) {
65135      return getProgramTime({
65136        playlist: this.masterPlaylistController_.media(),
65137        time: time,
65138        callback: callback
65139      });
65140    } // the player must be playing before calling this
65141    ;
65142
65143    _proto.seekToProgramTime = function seekToProgramTime$1(programTime, callback, pauseAfterSeek, retryCount) {
65144      if (pauseAfterSeek === void 0) {
65145        pauseAfterSeek = true;
65146      }
65147
65148      if (retryCount === void 0) {
65149        retryCount = 2;
65150      }
65151
65152      return seekToProgramTime({
65153        programTime: programTime,
65154        playlist: this.masterPlaylistController_.media(),
65155        retryCount: retryCount,
65156        pauseAfterSeek: pauseAfterSeek,
65157        seekTo: this.options_.seekTo,
65158        tech: this.options_.tech,
65159        callback: callback
65160      });
65161    };
65162
65163    return VhsHandler;
65164  }(Component);
65165  /**
65166   * The Source Handler object, which informs video.js what additional
65167   * MIME types are supported and sets up playback. It is registered
65168   * automatically to the appropriate tech based on the capabilities of
65169   * the browser it is running in. It is not necessary to use or modify
65170   * this object in normal usage.
65171   */
65172
65173
65174  var VhsSourceHandler = {
65175    name: 'videojs-http-streaming',
65176    VERSION: version$4,
65177    canHandleSource: function canHandleSource(srcObj, options) {
65178      if (options === void 0) {
65179        options = {};
65180      }
65181
65182      var localOptions = videojs.mergeOptions(videojs.options, options);
65183      return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
65184    },
65185    handleSource: function handleSource(source, tech, options) {
65186      if (options === void 0) {
65187        options = {};
65188      }
65189
65190      var localOptions = videojs.mergeOptions(videojs.options, options);
65191      tech.vhs = new VhsHandler(source, tech, localOptions);
65192
65193      if (!videojs.hasOwnProperty('hls')) {
65194        Object.defineProperty(tech, 'hls', {
65195          get: function get() {
65196            videojs.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.');
65197            return tech.vhs;
65198          },
65199          configurable: true
65200        });
65201      }
65202
65203      tech.vhs.xhr = xhrFactory();
65204      tech.vhs.src(source.src, source.type);
65205      return tech.vhs;
65206    },
65207    canPlayType: function canPlayType(type, options) {
65208      if (options === void 0) {
65209        options = {};
65210      }
65211
65212      var _videojs$mergeOptions = videojs.mergeOptions(videojs.options, options),
65213          _videojs$mergeOptions2 = _videojs$mergeOptions.vhs.overrideNative,
65214          overrideNative = _videojs$mergeOptions2 === void 0 ? !videojs.browser.IS_ANY_SAFARI : _videojs$mergeOptions2;
65215
65216      var supportedType = simpleTypeFromSourceType(type);
65217      var canUseMsePlayback = supportedType && (!Vhs.supportsTypeNatively(supportedType) || overrideNative);
65218      return canUseMsePlayback ? 'maybe' : '';
65219    }
65220  };
65221  /**
65222   * Check to see if the native MediaSource object exists and supports
65223   * an MP4 container with both H.264 video and AAC-LC audio.
65224   *
65225   * @return {boolean} if  native media sources are supported
65226   */
65227
65228  var supportsNativeMediaSources = function supportsNativeMediaSources() {
65229    return browserSupportsCodec('avc1.4d400d,mp4a.40.2');
65230  }; // register source handlers with the appropriate techs
65231
65232
65233  if (supportsNativeMediaSources()) {
65234    videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
65235  }
65236
65237  videojs.VhsHandler = VhsHandler;
65238  Object.defineProperty(videojs, 'HlsHandler', {
65239    get: function get() {
65240      videojs.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.');
65241      return VhsHandler;
65242    },
65243    configurable: true
65244  });
65245  videojs.VhsSourceHandler = VhsSourceHandler;
65246  Object.defineProperty(videojs, 'HlsSourceHandler', {
65247    get: function get() {
65248      videojs.log.warn('videojs.HlsSourceHandler is deprecated. ' + 'Use videojs.VhsSourceHandler instead.');
65249      return VhsSourceHandler;
65250    },
65251    configurable: true
65252  });
65253  videojs.Vhs = Vhs;
65254  Object.defineProperty(videojs, 'Hls', {
65255    get: function get() {
65256      videojs.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.');
65257      return Vhs;
65258    },
65259    configurable: true
65260  });
65261
65262  if (!videojs.use) {
65263    videojs.registerComponent('Hls', Vhs);
65264    videojs.registerComponent('Vhs', Vhs);
65265  }
65266
65267  videojs.options.vhs = videojs.options.vhs || {};
65268  videojs.options.hls = videojs.options.hls || {};
65269
65270  if (videojs.registerPlugin) {
65271    videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
65272  } else {
65273    videojs.plugin('reloadSourceOnError', reloadSourceOnError);
65274  }
65275
65276  return videojs;
65277
65278})))};

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.